Skip to content

WordPress

Most of the sites an agency looks after run WordPress. One Application Password per website opens the same host several ways at once. One of them supplies a marketing source nobody else has: how often a client’s brand turns up, and gets linked, in AI answers. And under the same login an agent reads and edits that site’s pages, forms and media.

The WordPress card on a website’s detail page (Domeinen & websites → Websites → the site). There is deliberately no menu item: WordPress is not a place you go, it is something a website has — the same reasoning that keeps Cloudflare on a domain rather than in the sidebar.

Switch the integration on under Instellingen → Integraties (Settings → Integrations). It needs the Websites module, because that is what the connection hangs off.

  1. In WordPress, go to Users → Profile and scroll to Application Passwords. Create one and give it a name you will recognise later (“schakl”, say).
  2. In schakl., open the website and fill in the site address, the WordPress username and that password on the WordPress card.
  3. Press Controleren (Verify).

The password is stored encrypted, is write-only through the API, and never appears in a response, a log line or the activity trail. The trail records that it changed, never what to.

Six independent probes run, and none of them gates another, because in reality those states are independent: Rank Math is routinely absent from a site whose posts API is perfectly healthy, and the MCP plugin from a site where Rank Math works fine.

CapabilityWhat a ✓ meansWhat a ✗ means
restThe password reaches the WordPress REST APIThe credential itself was refused
adminThat user is an administratorValid credential, but not an administrator
abilitiesWordPress 6.9’s Abilities API is presentWordPress is older than 6.9
rankmath_aivRank Math answers on AI VisibilityPlugin absent, or no Content AI subscription
mcpThe MCP Adapter plugin is installedIt is not
bridgeThe schakl WordPress MCP Bridge plugin is installed, with its versionIt is not

Two rules follow from that. A read that succeeds outranks a probe that refuses: a credential that reached the ordinary REST API is active even if the others failed, and only one refused by every probe is called refused. And unreachable and not WordPress are kept separate from refused, because neither says anything about the password — reporting them as an auth failure sends somebody off to re-mint a credential that was never wrong.

Every ✗ carries the site’s own error text, untranslated, because it is a quote: rest_no_route, rest_forbidden, aiv_unauthorized. A ✗ with no explanation is the one state nobody can act on.

Rank Math registers its AI Visibility features as WordPress Abilities, with two lines of metadata: show in REST, and expose over MCP. Those two lines are the whole story — they put the same functions on WordPress core’s REST route and on the MCP Adapter plugin’s MCP server, and both authenticate the way any REST caller does.

SurfaceWhat it is
wp/v2The ordinary REST API: posts, media, users, plugins
wp-abilities/v1WordPress 6.9’s Abilities API, opt-in per ability
mcp/<server>The MCP Adapter plugin’s MCP server
rankmath/v1/ai-visibilityRank Math’s own AI Visibility route

With the credential in place, link a marketing source of type Rank Math to this website under Marketing on the client. Without a connected website that link is refused at once, rather than turning up as a red line on tomorrow morning’s sync.

Five figures, and because none of them is a number a marketeer already owns, each one carries its own sentence on screen — written out where there is room, as a tooltip where there is not, in the same component for your team and for the client’s portal.

FigureWhat it countsScale
AI visibility scoreRank, mentions, citations and sentiment folded into one0–100
MentionsTimes the brand was named in answers to the tracked promptsCount
CitationsThe subset of those that also linked to the brand’s siteCount
Average sentimentHow positively the brand is describedPercentage
Average rankThe brand’s mean place among the brands an answer names1 is best

Why the trend line is ours and not Rank Math’s

Section titled “Why the trend line is ours and not Rank Math’s”

Rank Math keeps no history: every upstream path answers “latest” only. So the nightly sync writes one snapshot per day and the trend line exists because we store those snapshots. Filling a thirty-day window with thirty identical rows would be a flat line that looks like measurement.

Analyses also run on the cadence of your Rank Math plan — weekly by default — so two consecutive snapshots may legitimately carry the same number. When it was last analysed travels with the figures, so a report can say what it is actually comparing rather than announcing a 0% week.

The same login is how an agent reads and edits a client site’s content, from schakl.’s MCP server, including on sites that register no abilities of their own. A site is a parameter, never a tool: the /mcp/wordpress section is the same set of tools whether your agency looks after four sites or four hundred. A tool per site would be a list that differs per client, and a chat client puts every tool in the model’s context on every turn. What differs per site, which plugins, which post types and which abilities, is answered by that site’s summary.

WhatPermission
Which site is this client’s, and a summary: WordPress and PHP version, post types, plugin namespaces, forms, abilities, multilingual or notwordpress.site.read
Read pages, posts and any custom post type, with drafts, ACF fields and the WPML language; look up mediawordpress.content.read
Create a draft or edit something that is not live yetwordpress.content.write
Edit something a visitor sees, or set it livewordpress.content.publish
Read / edit Contact Form 7: template, field names, both mails, messageswordpress.forms.read / wordpress.forms.write
List abilities and run the ones that mark themselves read-onlywordpress.ability.read
Run any abilitywordpress.ability.run

WordPress has no staging: an edit to a published page is the broadcast. So the permissions are not split by which field you touch but by who sees the result. A member writes drafts; anything a visitor can see is wordpress.content.publish, admin only by default. That is decided by reading the record before the write, and the refusal comes before the site is asked anything. Editing a form sits with publish, because it is live the moment it saves and decides where a client’s leads land. An ability that does not mark itself read-only is treated as a write: better to refuse something harmless than to let something writing through.

Every write is a line in the site’s activity trail in schakl., naming the fields it touched. The client’s WordPress records nothing about us.

Whatever has no fixed route goes through one bounded passthrough: any call the site’s REST API takes, under the stored password. It exists because real client sites run WPML, LiteSpeed, FileBird and Link Genius, and a route per plugin is a list that rots. It is also the route that hands an agent a WordPress administrator, so its bounds are the design:

  • wordpress.rest.read for GET (member), wordpress.rest.write for anything else (admin only, never on a default key).
  • Writes to users, plugins, themes and settings are refused for everybody, before the site is asked. Those changes are made by a person in the site’s own admin. Reading the same routes is allowed.
  • Paths are relative to /wp-json/ and cannot climb out of it.
  • Every write is a trail line, and every answer is capped at 256 KB with what was left out named.

schakl WordPress MCP Bridge: page builders, hidden post types, uploads, forms, menus and WPML

Section titled “schakl WordPress MCP Bridge: page builders, hidden post types, uploads, forms, menus and WPML”

Many agency sites build a page out of an ACF repeater of typed blocks, each block type showing a handful of fields out of thirty. That cannot be edited through ACF’s REST integration, and none of it is fixable from the password’s side:

  • ACF’s validation ignores conditional logic, so a hidden required field blocks every write;
  • a repeater has to go back whole and saves by position, so changing one block means resending every block;
  • a post type not shown in REST does not exist there;
  • wp/v2/media only takes multipart, which no MCP tool can send.

So a WordPress plugin does it on the site itself: the schakl WordPress MCP Bridge (its own repository, schakl-wordpress-mcp-bridge, PHP 7.4+, GPL). It knows the field groups’ schema including when the editor hides a field, reads per block only the fields the editor shows, writes with conditional logic evaluated the way the editor does, reports every problem with its full path (blokken[2].titel) instead of stopping at the first, edits one block without touching the others, takes an image as a URL or base64, and handles WPML: languages, a linked translation with every reference swapped for its translation, and String Translation.

schakl. reaches the plugin through 33 bridge routes under the same login, and so through the same 33 tools in /mcp/wordpress however many sites there are:

WhatPermission
Versions, post types (including ones not shown in REST), taxonomies, options pages, menus, languageswordpress.site.read
The field schema; read records, terms, options pages and menuswordpress.content.read
Create or edit records and terms, upload media, create or connect a translationwordpress.content.write (+ publish for what is live)
Change ACF options pages and translated strings: live the moment they savewordpress.content.publish
Create a menu, rename it or change which theme locations it fills; add, edit, move and remove items, and put them in orderwordpress.content.publish
Trash or delete a record; delete a menu with all its itemswordpress.content.delete
Read Contact Form 7 forms with the fields parsed from the template and CF7’s own configuration errorswordpress.forms.read
Create or edit a form, or make its translationwordpress.forms.write
Delete a formwordpress.forms.delete

A record, a term or an options page can also be read as text rather than as a field tree: the title, the body without markup and the ACF fields in order, with choices by their label. The record list takes the same option, so an agent pulls every FAQ item with its answer in one call, at a fraction of the size.

Menus are edited in place: an item keeps whatever you do not send, can be pointed at another page or term, moved under another parent and positioned among its siblings, and a custom link’s URL can be changed. Deleting a whole menu is permanent and sits under the delete permission, like deleting a record.

Contact Form 7 is reachable two ways. The plain form routes above go through CF7’s own REST namespace and keep working on a site without the plugin. The bridge adds what that namespace never had: the fields CF7 parses off the template, its configuration validator’s objections (CF7 sends nothing while a mail is misconfigured), mails and messages merged over what the form holds so a changed recipient does not blank the subject, a delete, and WPML. A form deletes permanently (CF7 has no trash) and empties every page that embeds it, hence its own permission.

WPML knows a form two ways, and both are served. Where forms are a translatable post type, each language gets a linked copy of its own, and the answer carries the shortcode a page in that language embeds. Where the Contact Form 7 Multilingual add-on serves every language from one form, its texts are String Translation strings, read and written on the form itself.

Where the plugin is not installed the call says so itself, naming the plugin; on the WordPress card the verify shows which version is there, and under it whether the plugin can update itself: it updates itself (automatically, where WordPress is set to), a newer release is available, there is no GitHub token, or the update check failed. A site without a working token links to the plugin’s settings screen. A plugin older than 1.3.1 does not report this, and the card says not reported, never cannot update. A refusal from the plugin comes through unchanged, with the path to the field and the row, because a translated sentence without the row index sends an agent hunting.

PermissionWhat it opensDefault
wordpress.site.manageConnect, change the password, verify, disconnectAdministrator
wordpress.site.readSee whether this client’s site is connected, what the last verify found, and the summaryAdministrator, Member
wordpress.content.readRead pages, posts, media and bridge recordsAdministrator, Member
wordpress.content.writeWrite drafts, edit what is not live, upload mediaAdministrator, Member
wordpress.content.publishEdit anything a visitor sees, set something live, options pages, menus and stringsAdministrator
wordpress.content.deleteTrash or delete records, delete menusAdministrator
wordpress.forms.read / wordpress.forms.writeRead / edit Contact Form 7Member / Administrator
wordpress.forms.deleteDelete Contact Form 7 forms through the bridgeAdministrator
wordpress.ability.read / wordpress.ability.runRead-only abilities / any abilityMember / Administrator
wordpress.rest.read / wordpress.rest.writeThe REST passthrough: read / writeMember / Administrator

The read goes to members because “is this client’s site connected?” is a question an account manager asks while doing ordinary work. The manage permission stays admin-only and is deliberately not folded into websites.website.write.

  • Disconnecting forgets the password here; it does not revoke it at WordPress. That is the owner’s act on their own profile screen, and doing it as a side effect of tidying a list would break whatever else that password was minted for.
  • The card renders instantly, connected or not. It reads stored state and touches no outside service, so a website page loads at the same speed whether the client’s WordPress is up, down, behind a firewall or gone. Going and looking is what the Verify button is for.
  • A verify that succeeds clears the error a previous one set. A status flag that only ever turns on is a bug with a long tail.
  • Rank Math is read only. No creating a brand and no editing prompts. Writing to the site itself goes through the MCP routes above, under their own permissions.
  • Restricted colleagues see only their own clients’ sites. A website belongs to its domain’s client, which is two joins away — and it is enforced, not assumed.