SEO Audit → Fix → Report Workflow
End-to-end agency workflow combining wp_audit_seo_bulk (the audit) with the four *_bulk_update_meta writers (the fix), then turning the audit's structured response into a client-facing report. Designed for the recurring rhythm most agencies run monthly against every portfolio site: scan for problems, fix in bulk, verify, hand the client a summary.
- Monthly SEO health check across a portfolio of client sites
- Onboarding a new site — establish a baseline of what needs fixing before the first content push
- After a site migration or theme swap — surface anything that got dropped or broken
- Post-content-audit follow-up — you know some pages have thin content; this quantifies which ones
- Pre-launch cleanup — pair with Site Launch Readiness to catch the on-page issues that the composer's home-page-only check doesn't cover
Prerequisites
- Royal MCP Pro installed and licensed (see Getting Started)
- MCP client connected to your site
- Cap:
manage_optionsfor the audit;manage_options+ per-postedit_postfor the bulk fix - An SEO plugin (Yoast, Rank Math, SEOPress, or All in One SEO) — the audit auto-detects which one is active. If none is active the audit falls back to reading WP native fields (
post_titleandpost_excerpt), but the bulk-fix writers require the matching plugin to be active - Optional: SiteVault Pro (for the
snapshot: trueflag on the audit call to fire a pre-audit backup)
The workflow at a glance
- Optional pre-audit snapshot — SiteVault backup fires before any read so you have a known-good restore point.
- Run
wp_audit_seo_bulk— scan the target post type, paginating through if needed. Response contains per-page findings + a rollup with issues_by_type and worst_offenders. - Bucket the findings by issue key — group posts by issue so each bulk-fix call touches a homogeneous batch.
- Dry-run the bulk fix — call the plugin-specific writer with
dry_run: truefirst. Review the diff. Commit with the returnedcommit_tokenwithin 5 minutes. - Re-audit — run
wp_audit_seo_bulkagain on the same slice. Issue counts should drop to reflect what you just fixed. - Format the report — the audit's
structuredContentis your report data. Ask the AI to shape it into whatever the client wants.
Steps 2–6 are all MCP calls. Nothing in this workflow requires opening wp-admin unless you want to spot-check a fix visually.
Step 1: Optional pre-audit snapshot
The audit itself is read-only, but the bulk fixes that follow are not. If you want a fresh restore point from just before this workflow started:
Run wp_audit_seo_bulk on post_type "page" with snapshot=true.The audit response will include a sitevault_snapshot object describing what happened:
status: okwithviaandid— SiteVault fired and returned a backup IDstatus: skippedwithmessage: 'SiteVault not installed — snapshot skipped, audit proceeds against live state.'— no SiteVault detected; audit still runs
Snapshot failure never aborts the audit — the audit is safe to run without one. You get informed either way.
Step 2: Run wp_audit_seo_bulk
Basic call
Run wp_audit_seo_bulk on post_type "post".Default: 100 posts per page, page 1, rollup on. If the post type has more than 100 posts you'll need to paginate (next section).
Response shape
Abbreviated to one representative page for readability:
{
"isError": false,
"content": [
{ "type": "text", "text": "SEO audit — 47 post audited. Top issues: 22× description_missing, 18× title_too_short, 9× no_featured_image. SEO plugin: yoast." }
],
"structuredContent": {
"post_type": "post",
"pagination": { "page": 1, "per_page": 100, "total_pages": 1, "total_posts": 47 },
"seo_plugin_detected": "yoast",
"pages": [
{
"post_id": 128,
"url": "https://example.com/why-half-of-your-plugins-are-eating-your-tttf/",
"title": { "value": "Why half of your plugins are eating your TTFB", "length": 47, "issue": null },
"description": { "value": "", "length": 0, "issue": "missing" },
"noindex": false,
"h1_count": 1,
"word_count": 942,
"image_count": 4,
"images_missing_alt": 2,
"featured_image": true,
"issues": [ "description_missing", "2_images_missing_alt" ]
}
// … 46 more page objects
],
"sitevault_snapshot_id": null,
"sitevault_snapshot": { "status": "skipped", "message": "…" },
"rollup": {
"total_pages_audited": 47,
"issues_by_type": {
"description_missing": 22,
"title_too_short": 18,
"no_featured_image": 9,
"images_missing_alt": 7,
"h1_missing": 3,
"title_missing": 2
},
"worst_offenders": [
{ "post_id": 128, "issue_count": 5 },
{ "post_id": 91, "issue_count": 4 }
// … up to 10 worst offenders
]
}
},
"undo": { "token": "…", "expires_at": "…", "ttl_hours": 168, "summary": "SEO audit — read-only, undo is a no-op" }
}The audit is read-only, but the response ships an undo token anyway for API consistency (so MCP clients that always look for one don't blow up). Redeeming it resolves to a no-op. The subsequent bulk-fix calls ship real undo tokens — those are the ones that matter.
Pagination
For post types with more than 100 posts (or if you want smaller page sizes for slower servers):
Run wp_audit_seo_bulk on post_type "post" with paginate={page:1, per_page:200}.Pagination caps: per_page minimum 1, maximum 500 (enforced by the tool regardless of what the schema minimum suggests). Loop through pagination.total_pages to cover the full portfolio.
Statuses included
The audit queries posts in these statuses: publish, draft, pending, private, future. Trashed posts are intentionally excluded — they don't appear in SEO surfaces so scoring them would inflate issue counts against posts nobody sees.
Disable the rollup
If you only want the per-page findings (no aggregate):
Run wp_audit_seo_bulk on post_type "post" with rollup=false.Response omits the rollup key. Useful when piping into your own aggregation script.
Step 3: Bucket the findings by issue key
Each per-page object has an issues array. Group posts by which issue is on their list so the fix batches are homogeneous — all title_too_short in one call, all description_missing in the next, etc.
The full issue-key catalog
| Issue key | Trigger | Fixable via bulk_update_meta? |
|---|---|---|
title_missing | Both SEO plugin title and post_title are empty | Yes — pass a title value |
title_too_short | Title length < 30 chars | Yes — pass a longer title |
title_too_long | Title length > 60 chars | Yes — pass a shorter title |
description_missing | Both SEO plugin description and post_excerpt are empty | Yes — pass a description value |
description_too_short | Description length < 120 chars | Yes — pass a longer description |
description_too_long | Description length > 160 chars | Yes — pass a shorter description |
noindex_true | Post is set to noindex | Yes — pass noindex: false (see per-plugin notes below) |
h1_missing | No <h1> in post_content | No — edit the post content in the editor |
h1_multiple | More than one <h1> in post_content | No — edit the post content |
no_featured_image | Post has no featured image set | No — set via wp_set_featured_image (Free tier) after uploading media |
N_images_missing_alt | N images in post_content lack an alt attribute | No — edit each image's alt text in the Media Library or editor |
Rollup bucketing: the rollup normalizes compound keys like 3_images_missing_alt into a single images_missing_alt bucket for counting purposes. The per-page objects still show the compound version so you know how many images per post need alt text.
Length thresholds (constants)
- Title: 30–60 chars (min
TITLE_MIN, maxTITLE_MAX) - Description: 120–160 chars (min
DESC_MIN, maxDESC_MAX)
These match Yoast / Rank Math / SEOPress / AIOSEO / SEObolt's own default snippet-preview ranges. Not configurable via filter as of 1.0.2.
Step 4: Bulk-fix with the SEO-plugin-specific writer
Four writers, one per supported plugin. Pick the one that matches the value in the audit's seo_plugin_detected field:
yoast_bulk_update_metarankmath_bulk_update_metaseopress_bulk_update_metaaioseo_bulk_update_meta
All four share the same argument shape, two-step commit pattern, drift detection, and 168h undo TTL. See the SEO Agency Suite reference for the full argument grammar including the 3-state field model (OMIT / null / value) that keeps you from accidentally overwriting untouched fields.
Two-step commit walkthrough
Step 4a: Dry-run to preview
Call yoast_bulk_update_meta with dry_run=true and updates=[
{ post_id: 128, description: "Half the plugins on the average WordPress site are quietly eating your TTFB. Here's how to spot them, and what to swap them for." },
{ post_id: 91, description: "A 12-minute walkthrough on turning a slow WooCommerce cart into a sub-second checkout." }
// … up to 100 rows per call
].Response includes a per-row preview showing what the value would change from and to, plus a commit_token to redeem in step 4b. Nothing is written yet.
Step 4b: Commit with the returned token
Call yoast_bulk_update_meta with commit_token="{token from step 4a}".The tool re-reads the current values of every row and compares against what they were at dry-run time. Any post whose value changed between dry-run and commit gets skipped with a drift warning (someone else edited it in that 5-minute window). The rest apply.
If you take more than 5 minutes between dry-run and commit, the token expires — you'll need to re-dry-run. Tokens also can't be redeemed twice; a successful commit invalidates the token immediately.
Row cap
Each call handles up to 100 rows. For portfolio-wide fixes, chunk your buckets into calls of 100 or fewer.
The noindex quirks
On Yoast, Rank Math, and AIOSEO, passing noindex: false explicitly sets the post to index. On SEOPress, both noindex: false and noindex: null delete the meta row (reverting to the site default). SEOPress just doesn't have a "force index" concept — if the site default is index-all, that's what you get; if it's noindex-all, you'd need to change the site default instead.
AIOSEO writes to legacy post_meta AND to a custom wp_aioseo_posts table on modern versions. The writer dual-writes to keep both surfaces coherent. Posts that AIOSEO has never touched (no row in wp_aioseo_posts) surface in warnings — open them once in the AIOSEO editor first to seed a row, then re-run the bulk fix.
Step 5: Re-audit to verify
Run wp_audit_seo_bulk a second time on the same post type. Compare the two issues_by_type rollups:
Before:
description_missing: 22
title_too_short: 18
After (same 100-row batch fixed):
description_missing: 0 ← fixed the 22, no new ones
title_too_short: 18 ← untouched batch, still thereIf a fixed issue count didn't drop as expected, likely causes:
- Drift-detection skipped some rows — check the commit response's
skippedarray for post IDs that were touched during your 5-minute window - Wrong SEO plugin writer was called (audit detected Yoast, you called
rankmath_bulk_update_meta) — checkseo_plugin_detectedin the audit response - A subset of your rows had a length that still hit the threshold (e.g. you set a description that's 118 chars — still under the 120-char
DESC_MIN)
Step 6: Format the report
Royal MCP Pro doesn't ship a "generate a client PDF" button — the report data is the audit's structuredContent. The AI you're using is what formats it. Ask for whatever shape works for the client.
Executive summary shape
Take the two wp_audit_seo_bulk responses (before + after) and write me an
executive summary in 5 bullets for a non-technical client. Mention what
was audited, the top 3 issue types found, how many were fixed, and what's
left as a follow-up.Punch-list shape (for the client's dev/content team)
From the after-fix audit response, list every page with remaining issues
grouped by URL. For each URL show which issues remain and what needs to
happen to close each one (edit content vs. edit meta vs. add featured image).Trend-over-time shape (agency retention)
Log each month's issues_by_type rollup to a spreadsheet. Ask the AI to visualize the trend across 3–6 months so the client sees the graph go down over time.
content[0].text fieldThe audit's top-level content field always contains a single-sentence summary in the format: "SEO audit — {N} {post_type} audited. Top issues: {N× issue1, N× issue2, N× issue3}. SEO plugin: {detected}." Great as a one-line agency status update in Slack / Discord / email without any AI formatting.
Common scenarios
Monthly agency check across 20 portfolio sites
Same audit call on each site. Log issues_by_type rollups to a shared spreadsheet. The client sites where an issue count creeps up month over month are your action list.
New-site onboarding baseline
Run the audit on post and page post types (and any custom post types the site uses). The rollup is your first-month project scope. Fix the top-3 buckets in bulk, hand the client a before/after report at the end of month 1.
Post-migration cleanup
Migrations frequently lose SEO meta (theme change wipes theme-stored meta, migration tools skip custom fields, plugin swap doesn't map old-plugin keys to new-plugin keys). Run the audit right after migration; the resulting rollup shows exactly what got lost. Bulk-restore in one batch.
Thin-content follow-up
Content audit surfaces "these 40 pages have < 300 words." Cross-reference with wp_audit_seo_bulk's word_count field per page. Anything under 300 words AND with a no_featured_image or description_missing issue is a genuinely thin, un-loved page. Prioritize accordingly.
Still Stuck? Two-Step Support Path
If the audit or bulk-fix calls aren't producing what you expect, work through these two steps in order.
Step 1: Start with the Royal MCP Troubleshooting Guide
Royal MCP Troubleshooting — Start Here covers MCP-layer issues (connection, discovery, auth).
For Pro-specific patterns:
Step 2: Email priority support
If you've worked through Start Here and the relevant Pro-specific doc and the workflow still isn't working, email priority support from your purchase email address at support@royalplugins.com. Priority email support is included with your license — typical response within 24 hours. Never include your license key in email; we look it up from your purchase address.
- Your hosting provider
- Royal MCP Pro version
- Which SEO plugin is active + its version (the audit's
seo_plugin_detectedfield answers this) - Which MCP client and how the call was phrased
- Post type being audited + approximate total post count
- The full audit response if you're reporting an audit issue, or the full dry-run + commit responses if you're reporting a bulk-fix issue
- Any drift-warnings from the commit response if you're troubleshooting missed rows
- Screenshot of the Pro tool row in Audit Log with View Details expanded