AI Godmode
WordPress plugin that gives an AI agent server-administrator access, plus a Tool Drawer that cuts MCP token use. Everything ships switched off.
by John Pozadzides · github.com/johnpoz/ai-godmode · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/johnpoz/ai-godmode/archive/refs/heads/main.zipA self-contained WordPress plugin that registers server-administrator actions as native WordPress Abilities (core since 6.9), so an AI agent can discover and run them over MCP through a bridge. Every ability ships switched off. The name is the safety argument.
Personal project by John Pozadzides. Not affiliated with Texas Metal Works. Free, GPLv2 or later.
- Plugin page and hosted download: https://letsmcp.org/plugins/ai-godmode/
- Code: https://github.com/johnpoz/ai-godmode (the Gitea instance at gitea.johnp.me is the working mirror)
- Why it exists and the measured results: the OneMansBlog post linked from the plugin page
Status
v0.8.3. 65 abilities across thirteen families (25 read, 40 write), plus a SiteGround cache purge on SiteGround hosting, ten more when Code Snippets is active and two more when Easy MCP AI is active, every one default-off, capability-checked, and audit-gated for mutations. The off-site audit log is built, deployed and proven live, and is now isolated per site: one bucket, one ingest key and one viewer key each, so connecting a new site cannot disturb a site already recording. The admin interface is tabbed, with a Help tab and a two-part alarm.
| Family | Abilities |
|---|---|
| options | read-option, write-option, list-options, delete-option, transient-get, transient-set, transient-delete |
| database | db-list-tables, db-describe-table, db-query-read, db-query-write, db-export |
| filesystem | fs-list, fs-read, fs-search, fs-write, fs-patch, fs-mkdir, fs-copy, fs-move, fs-delete, fs-chmod, fs-zip, fs-unzip |
| plugins | plugin-list, plugin-install, plugin-activate, plugin-deactivate, plugin-update, plugin-delete |
| themes | theme-list, theme-install, theme-switch, theme-update, theme-delete |
| users | user-list, user-get, user-create, user-update, user-delete, role-list, role-create, role-delete, role-add-cap, role-remove-cap, app-password-list, app-password-create, app-password-delete |
| cron | cron-list, cron-run, cron-schedule, cron-unschedule |
| diagnostics | get-environment, site-health, php-info, constants, hooks-inspect, error-log-tail |
| run-php | run-php |
| core | get-reference |
| site | http-fetch, cache-purge, permalinks-flush |
| drawer | find, call, wp-find, wp-call (the last two present only when Easy MCP AI is active) |
| siteground | siteground-purge-cache (present only when Speed Optimizer is active) |
| code-snippets | snippets-list, snippets-get, snippets-create, snippets-update, snippets-replace, snippets-activate, snippets-deactivate, snippets-trash, snippets-run, snippets-revert (present only when Code Snippets is active and compatible) |
25 read, 41 change something. In the interface they are labelled READ and WRITE; "mutation" was removed as a user-facing word because it meant nothing to anyone who had not read the source.
A WRITE badge turns into WARNING only while the off-site log has stopped recording, which is exactly when that ability is being refused. The badge is a live readout of what will happen if the ability is called, not a permanent label on the category. A label that is always on gets ignored.
The Tool Drawer
Every tool definition an MCP client holds is sent back to the model with every
request. On onlyreason.org that was 243 tools and 70,314 tokens per request
(2026-10-07, counted with the Anthropic token-count API), of which this plugin's
70 abilities were 11,403. So since 0.7.0 the connector lists a small core set
and the rest sit in a drawer: still registered, switched and audited exactly as
before, reachable through godmode/find (search, returns the input schema) and
godmode/call (run by name through every gate the ability has), and for Easy
MCP AI's own tools through godmode/wp-find and godmode/wp-call, which run
them through Easy MCP's complete call pipeline. The find descriptions carry the
names of everything currently in the drawer, grouped by family, so the model
knows what it can ask for.
No MCP server plugin is patched. They all answer through the WordPress REST
layer, and includes/class-surface.php shortens any JSON-RPC tools/list reply
through core's rest_post_dispatch filter, whichever server sent it (Easy MCP
AI, the WordPress MCP Adapter, or the next one). Their call paths never consult
the listing. The always-loaded set is operator-configurable on the Tool Drawer
tab, one expandable section per family or plugin with a master checkbox; the
switch ships on because it removes exposure rather than granting power. Other
plugins' ability tools stay listed unless "Put other plugins' abilities in the
drawer too" is on; a tick on an individual tool wins either way. What the operator gives up is the client-side per-tool prompt for
drawer tools, since to the client the drawer is one tool; the gating moves
entirely server-side.
How The Switches Work
An ability runs only when the master switch is armed AND its own switch is on.
Both default off. The master switch sits above the tab bar under the heading
"Arm / Disarm Master Switch", deliberately the loudest element on the screen,
because the predictable failure is somebody switching on every ability and never
realising one switch above them all is still off. Exposure flags (meta.public, meta.show_in_rest) follow the
effective state, so a switched-off ability is neither listed nor executable.
Exposure is never the security boundary: every call runs a manage_options
capability check regardless.
A newly registered ability is always absent from the enabled map, which reads as false. New power is never granted quietly by an update.
Guardrails carried by the framework: filesystem access is confined to the
WordPress root and refuses path escapes, protected directories, and writes to
wp-config.php, the root .htaccess, .env files, .git trees, symlinks and the
plugin's own files; database reads are SELECT-only and row-capped by SQL;
secrets (salts, keys, passwords, user_pass, secret-looking keys inside
option values) are scrubbed from files, query rows and constants, and refused
from options, including every godmode_ option of the plugin's own; the plugin
refuses to deactivate or delete itself, to flip its switches through SQL, to strip
manage_options from the administrator role, or to delete the last
administrator or the current user. run-php is total control and is labelled as
such: it is bound by none of the above, because it is code rather than an
ability with rules.
Audit
Every execution writes an intent record before the callback runs
(wp_before_execute_ability) and a completion record afterwards. Records carry a
per-site sequence number and a SHA-256 hash chain. Refusals are recorded as
failures rather than as silence.
Mutations refuse to run when a configured sink cannot record the intent, unless
the caller passes acknowledge_unlogged: true; the override is itself logged as
a gap marker. Reads go to the local ring buffer only; mutations also go to every
configured remote sink.
The off-site log
A local log is worthless against the thing it exists to record: this plugin can
delete anything on the server, run-php included, so anything it writes it can
also erase. The record has to leave the machine before each action runs.
worker/audit-sink.js is an append-only Cloudflare Worker in front of an R2
bucket, deployed onto the operator's own account. Two layers keep records there:
- The Worker is the only door. Routes are
health,ping,append,query,view. There is no delete route in the source and no call to any R2 delete or overwrite method, so no key reaches one. An ingest key reaches only append and lives on the site; a viewer key reaches only query and never touches the site. - R2 enforces it underneath. Every append is a conditional write
(
onlyIf: { etagDoesNotMatch: "*" }) on a key derived from the sequence number, so a sequence number cannot be reused. A bucket lock blocks deletion below the Worker entirely.
Provisioning takes one pasted Cloudflare token, builds all of the above, mints
both keys, then discards the token permanently. It is never written to the
database: a token that can create Workers, sitting in an options row run-php
can read, would turn a compromise of the site into a compromise of the whole
Cloudflare account.
The honest limit: this is tamper evidence, not tamper proofing. The account
owner can remove the lock and delete. What is eliminated is the AI doing it.
See worker/README.md.
The alarm
When the off-site log stops recording, two things happen. A banner appears on every admin screen, dismissible for twelve hours and returning immediately if the failure changes. And a red mark appears on the Settings and AI Godmode menu items, which cannot be dismissed and stays for as long as the problem does.
The status check calls ping, which validates the ingest key. It deliberately
does not use health, which takes no key and always answers: an earlier version
did, and consequently reported the log as Live while every mutation was in fact
being refused.
Verification
Everything here runs outside WordPress.
php tools/check-schemas.php > /tmp/schemas.json # definitions, size budget
python3 tools/check-schemas.py < /tmp/schemas.json # JSON Schema Draft 2020-12
php tools/check-viewer-chain.php # hash chain and tamper cases
php tools/check-autoload.php # class to filename resolution
php tools/render-admin.php # every admin tab, every state
php tools/check-code-snippets.php # Code Snippets family and guard
php tools/check-hardening.php # 0.6.2/0.6.3 security rules, executed
php tools/check-surface.php # tool drawer: listing filter, index, four abilities
node worker/test/check-php-json-encoding.mjs # JS encoder vs real PHP
Inside WordPress, as an administrator at init or later (the Abilities API
refuses to initialize earlier). tools/verify-live-v0.2.0.php exposes
godmode_v2_verify(), which exercises every family by real execution including
negative and guardrail tests, restoring state afterwards:
require 'tools/verify-live-v0.2.0.php';
echo wp_json_encode( godmode_v2_verify() );
Against a live endpoint:
AUDIT_BASE=https://<worker>.workers.dev \
AUDIT_INGEST=... AUDIT_VIEWER=... ./worker/test/live-matrix.sh
Current results: 78 definitions within budget, 156 schemas valid, 13 of 13 chain checks, 102 classes resolvable, 60 of 60 tool drawer checks, every admin tab rendering in all three states, 8 of 8 encoder cases byte-exact, 33 of 33 live Worker checks.
Layout
ai-godmode.php plugin header, constants, autoloader, boot
uninstall.php option cleanup
includes/class-plugin.php bootstrap, heartbeat schedule
includes/class-settings.php switch and configuration storage
includes/class-registrar.php wp_register_ability wrapper with all gates
includes/class-audit.php audit manager, hash chain, sink fan-out
includes/interface-audit-sink.php
includes/class-ring-buffer-sink.php local record
includes/class-cloudflare-sink.php off-site record
includes/class-cloudflare-provisioner.php one-token setup and key rotation
includes/class-audit-viewer.php authoritative chain verification
includes/class-health.php is the off-site log actually recording
includes/class-notices.php the banner and the admin menu mark
includes/class-redactor.php denylist and payload redaction
includes/class-admin.php tabbed settings screen
includes/class-admin-ui.php design system, tabs, badges, help tips
includes/class-sink-admin.php Off-site Log and Maintenance tabs
includes/class-help-admin.php Help tab, fifteen sections
includes/class-mcp-bridge.php optional adapter registration, default off
includes/class-surface.php the tool drawer: listing filter and drawer index
includes/abilities/ one class per ability
worker/audit-sink.js the append-only endpoint, shipped in the zip
worker/test/ encoder differential and live matrix
tools/ schema, chain, autoload and render harnesses
docs/kb/ copies of the knowledge base pages
Project rules
- Verify by execution, never inspection alone. Report pass or fail per step.
- Everything ships default-off. The create-disabled invariant never changes.
- John deploys, never Claude.
- No em or en dashes anywhere, including in shipped files and commit messages.
- Definitions stay under about 2 KB; prose belongs in
godmode/get-referenceand the Help tab, not in schemas. - Push to Gitea in the same session the code is written; GitHub is mirrored from it.
Knowledge Base
Design, decisions and run results live in the personal KB (kb.johnp.me), shelf
"Web Development Projects", book "AI Godmode". Copies are in docs/kb/. Start
with cowork-handoff-start-here.md, then
off-site-audit-log-build-and-verification.md and
v0.3.x-status-light-and-interface-rebuild.md.