Completing the OAuth happy path
Three improvements surfaced from running v1.0.0 against a real Claude Desktop client. Each one was needed for the OAuth flow described in v1.0.0 to actually complete end to end on a typical WordPress install — they ship together as v1.0.1 so the entire happy path becomes reachable in a single upgrade.
- Robust gate priority on the REST filter chain. The
Resource_Server hook now runs at priority 11 (was 9), so its 401 + WWW-Authenticate response is the last word in the chain even when another plugin (ACF Pro, for example) registers a void-returning rest_pre_dispatch callback at priority 10. On installs where that pattern existed, MCP clients previously couldn't reach the OAuth discovery anchor; they now do.
- OAuth discovery at the standard well-known URLs.
/.well-known/oauth-authorization-server (RFC 8414 §3) and /.well-known/oauth-protected-resource (RFC 9728) are now served at the site root, which is where spec-compliant clients construct the URL and look. The previous /wp-json/… paths remain live as back-compat aliases — both serve identical content. Claude Desktop's mcp-remote bridge can now complete autonomous OAuth bootstrap.
- Sign in once and continue to consent. After logging in at
wp-login.php to authorize an MCP client, the consent screen now actually renders. WordPress's default REST cookie-auth gating expects an X-WP-Nonce header that server-driven redirects from wp-login don't carry — the authorize endpoint now re-derives the current user from the auth cookie directly so the post-login redirect lands on the consent screen instead of looping back to login.
- Tighter release build. Build script now refuses to ship a per-machine
.claude/ directory and adds zip-size guards (warn at 500 KB, hard-fail at 2 MB) so a future bypassed --no-dev install can't quietly produce a fat-vendor zip.
If you hit the wp-login loop on v1.0.0: v1.0.1 is the fix. Upgrade and the OAuth consent screen will land as designed.
Test suite: 319 tests / 1,076 assertions, all green.
Download v1.0.1 .zip
Remote MCP over OAuth — no bridge required
The v1.0 milestone. Paste https://your-site.com/wp-json/mcp/mcp-adapter-default-server straight into Claude.ai's "Add custom integration" UI and connect — no Node bridge, no command-line setup, no companion services. The plugin is now both the OAuth Authorization Server and the Resource Server, so a fresh WordPress install can stand up a remote-MCP endpoint by itself.
- OAuth 2.1 + PKCE (S256). Open Dynamic Client Registration (RFC 7591) means Claude.ai and other MCP clients can register and connect without any pre-shared secret. HTTPS is hard-required, with a localhost loopback exception for dev and a filter for reverse-proxy TLS-termination setups.
- Per-ability scopes. Every registered ability maps 1:1 to its own OAuth scope (
mcp.ping, mcp.rest-call, mcp.set-post-blocks, …). The consent screen groups them as Read / Write — reads pre-checked, writes require an explicit tick — matching the same asymmetric posture the plugin has used since v0.2.
- Opaque tokens, hashed at rest. SHA-256 in the database; plaintext returned to the client exactly once. Default TTLs: 1h access, 30d refresh (both filterable). Refresh-token rotation with family-wide reuse detection — a replayed refresh token revokes the entire token family.
- RFC 8414 + 9728 discovery.
.well-known/oauth-authorization-server and .well-known/oauth-protected-resource are live, and the Resource Server's 401 emits a WWW-Authenticate: Bearer resource_metadata="…" header so MCP clients complete discovery autonomously.
- OAuth-only on MCP routes. The rest of the WordPress REST surface is untouched — Application Passwords, cookies, and existing integrations keep working exactly as before. Only the
/mcp/* namespace requires a Bearer token.
- Revocation + housekeeping. RFC 7009
/oauth/revoke endpoint. A daily WP-Cron tick prunes expired authorization codes and revoked tokens past a configurable grace window (default 30 days).
- OAuth event audit log. A dedicated
ppok_ai_oauth_events table records every client_registered, consent_granted, token_issued, token_refreshed, token_revoked, and reuse_detected event — separate from the existing ability-invocation log so each has its own lifecycle and retention story. Each MCP-attributed ability event now also carries the actor_token_id that authenticated the call.
Bridge users: if you were running the Automattic Node bridge against this site, MCP routes are now OAuth-only — the bridge will need updated credentials, or you can drop the bridge entirely and connect direct. App Password setups against non-MCP routes are unaffected.
Admin UI follow-up: Tools → Ppok OAuth ships with scaffold tabs; the active-clients / active-tokens viewer with revoke buttons and the OAuth-events table view land in a follow-up release. Underlying data is being written today — only the rendering layer is queued.
Test suite: 307 tests / 1,013 assertions, all green.
Download v1.0.0 .zip
Block templates by example
A new read-only ability that infers a block's schema empirically — by sampling how the block is actually used on the site — plus a framework-adapter seam so plugin-specific data shapes (ACF, Gutenbricks, future others) can layer richer metadata on top. Closes the cold-start gap where an AI knows a block exists (from v0.4.0's blocks-registered) but doesn't know what to put in it.
ppok/block-template — samples up to 20 existing instances of a block and returns keys_observed (with type, present-rate, redacted sample values), required_keys, paired_keys (with role hints like value/label and src/alt), and variant_signals. Two-level flatten on associative attrs so nested shapes like ACF's data.<field> surface cleanly.
- Works on opaque blocks. Gutenbricks-style attrs with names like
gb-vdxvia / gb-pqmvuv still get useful pairings via URL-shape detection and a length-differential heuristic — the shorter, repeated value gets tagged as the label, the longer one as the value. No registered schema required.
- Framework adapter seam. A new
ppok_ai_toolkit_framework_adapters filter lets third parties register a Framework_Adapter instance that overlays plugin-native metadata onto the empirical baseline. Two built-ins ship: ACF (projects field-group fields whose location rules target the block) and a Gutenbricks placeholder (documents the future enrichment point until Gutenbricks exposes a stable introspection API).
- Honest output. Redacted sample values (80-char truncation),
<list[N]> / <map[N]> elision for complex values, mixed type fallback when no single type dominates, and an adapters_consulted list so callers can tell which overlays contributed.
Test suite: 246 tests / 789 assertions, all green.
Download v0.4.6 .zip
Auto-updates, end to end
The piece that closes the loop on the self-hosted update channel started in v0.4.3 and v0.4.4. The plugin now registers a handler on the hostname-scoped update_plugins_ppok-ai.com filter, fetches update.json, and reports new versions to WordPress directly — so the standard "Update Available" prompt fires on every site that runs v0.4.5 or later.
- Native WordPress update flow. No third-party update library. WP's built-in cron job (
wp_version_check) hits the new handler, which translates the hosted JSON into the stdClass shape WP stores in its update_plugins site transient.
- Cached the right amount. Update payloads are cached for 1 hour in a site transient (1-minute negative cache on fetch failure); the existing "View details" channel from v0.4.4 stays at 6 hours. Shared
fetch() helper, separate cadences.
- Forgiving JSON contract. The translator accepts either
version or new_version, plus aliases homepage/url and download_url/package, so the hosting side can evolve without breaking consumers.
Note for existing installs: sites currently running v0.4.3 or v0.4.4 don't yet have the update-check handler — v0.4.5 needs to be installed manually once. From v0.4.5 onward, auto-updates flow through WordPress as expected.
Test suite: 222 tests / 712 assertions, all green.
Download v0.4.5 .zip
Page outline + "View details" channel
A new read-only ability for cheap structural scans, and the companion to v0.4.3's Update URI header that wires up the wp-admin "View details" modal — so plugin info comes from the same source that drives auto-updates.
ppok/page-outline — depth-tracked flat outline of a post's block tree, returned as [{block_name, depth, brief_data_summary, anchor?}]. No rendered HTML, no full attrs, no innerContent — typically under 2KB even on pages where get-post-blocks returns 100KB+. Outline is the index; get-post-blocks is the content.
- Custom-block friendly summaries.
brief_data_summary falls back from stripped innerHTML to text-shaped attrs (content, text, title, alt, caption, label) so blocks from Gutenbricks, ACF Blocks, Spectra, and friends still summarize cleanly. Empty-freeform parser artifacts (whitespace blocks between adjacent delimiters) are filtered out — the outline reflects structure, not noise.
- "View details" modal now works. A new info channel hooks
plugins_api, fetches info.json, and populates the WordPress plugin-information modal. Cached in a site transient for 6 hours (1-minute negative cache on failure), URL overridable via the ppok_ai_toolkit_info_json_url filter.
Test suite: 217 tests / 698 assertions, all green.
Download v0.4.4 .zip
Dry-run authoring
Three abilities gain dry-run affordances so agents can verify their markup, scope, and routing decisions before committing anything to the database. Closes the loop that previously forced agents to ship to real drafts just to confirm a block tree round-trips cleanly.
ppok/set-post-blocks — validate_only flag. Runs the full gate stack and a serialize_blocks / parse_blocks round-trip, then returns {bytes_written_would_be, unknown_blocks, schema_violations, roundtrip_drift_blocks} without touching wp_update_post. Diagnostics are collected (the call never halts on a single failure in dry-run), and drift is computed structurally on the block tree, not on innerHTML whitespace.
ppok/replace-in-content — honest preview. dry_run (already default true) now predicts apply-path verdicts per candidate with a would_skip field (permission_denied or scope_blocked). The preview now matches what would actually happen on apply.
ppok/rest-call — opt-in validate_only. On POST/PUT/PATCH, short-circuits with a structured validate_only_not_supported error by default (since no WP core endpoint honors the flag). New ppok_ai_toolkit_validate_only_routes filter lets admins allowlist routes that genuinely support validation-only dispatch; for everything else, the recommendation is to POST with status: "draft" as the safest dry-run.
- Native WordPress auto-updates. Plugin now declares
Update URI: https://ppok-ai.com/plugins/ai-toolkit/update.json, so WP 5.8+ checks for new versions directly — no third-party update library.
Test suite: 201 tests / 640 assertions, all green.
Download v0.4.3 .zip
Release packaging tooling
Adds a composer build script that produces a clean, distributable plugin zip — slim production dependencies, version-stamp parity checks across the plugin header, version constant, and readme.txt stable tag, and per-version archives so each release stays addressable.
- One-command builds.
composer build stages a clean copy, runs composer install --no-dev --optimize-autoloader, and writes ppok-ai-toolkit-{version}.zip to a configurable target directory.
- Version parity, enforced. The build aborts if the plugin header
Version:, the PPOK_AI_TOOLKIT_VERSION constant, and readme.txt Stable tag: ever drift.
- Won't clobber a shipped version. Refuses to overwrite an existing zip, so the target directory doubles as a per-version archive.
No runtime plugin changes.
Download v0.4.2 .zip
Agent-DX patch
A ten-item polish pass that smooths over rough edges agents hit in the field. Together these changes cut token waste, sharpen response shapes, and make error payloads self-explanatory enough for an AI to handle them without human help.
- Structured permission errors.
ppok/get-post-blocks now returns a structured error payload carrying the gate name, required capability, and target post id — so the agent can adapt its next call instead of asking the user what went wrong.
- 87KB → 10KB block catalog. Optional
include_schema toggle on ppok/blocks-registered lets agents enumerate the block roster without dragging the full attribute schemas along.
- Rendered-HTML stripping by default.
ppok/rest-call drops content.rendered, title.rendered, excerpt.rendered, and guid.rendered on write responses; opt in with include_extended=true.
- Caller-specified slimming. New
slim_response_keys on ppok/rest-call lets the caller drop arbitrary top-level keys per row.
- Deterministic ordering.
ppok/list-posts gained orderby + order (whitelisted), defaulted to modified DESC, with a stable secondary sort by ID DESC.
- Friendlier schemas.
ppok/rest-call accepts route as a synonym for path; ppok/blocks-registered exposes a grouped namespaces summary.
Test suite: 194 tests / 608 assertions, all green.
Download v0.4.1 .zip
Block intelligence
Three new read-only abilities close the "agent guesses what exists" loop. Instead of scanning posts to discover what blocks and patterns live on the site, an assistant can now enumerate the registries directly.
ppok/blocks-registered — dumps every registered block type (core, theme, plugin, custom) with name, title, description, category, dynamic flag, and attribute schema. Optional namespace-prefix filter.
ppok/blocks-find-usage — takes a canonical block name and returns the posts that contain it with per-post counts. SQL LIKE prefilter plus a real parse_blocks + recursive innerBlocks walk, so a marker string inside another block's HTML never false-positives.
ppok/patterns-registered — exposes the block patterns registry (core, theme, plugin) that the REST API otherwise leaves unreachable. Optional category filter; include_content=false for cheap catalog enumeration.
Verified end-to-end against a real WordPress install with zero failures — no permission denials, no schema errors, no retries. Test suite: 184 tests / 560 assertions.
Download v0.4.0 .zip
Preview URLs for visual review
A new ppok/preview-url ability mints a short-lived (5-minute), HMAC-signed URL that bypasses the draft/pending/private redirect — closing the design-feedback loop. The AI proposes a change, hands the user a one-click link, and the user sees the draft rendered through the full theme without logging into wp-admin.
- Signed and expiring. Tokens are
hash_hmac sha256 over post id + expiry, keyed with wp_salt('auth'). Invalid or expired tokens are a no-op — WordPress serves its normal 404.
- Admin opt-in + audit logged. Even though no database mutation occurs, minting a bypass credential is a side effect — so the ability is gated by
abilities_enabled and every mint is recorded in the activity log.
Test suite: 149 tests / 457 assertions.
Live deploy verification
First end-to-end verification against a real WordPress install. The transport-stability layer from v0.2.0 held up under real traffic, and the run surfaced two opportunities to expand input handling.
- Wider input shapes.
replace-in-content now accepts the natural single-string forms for post_type and post_status (in addition to arrays), so callers don't have to wrap a single value in a list.
- Attribute-aware find/replace. Many modern block plugins (Gutenbricks, ACF Blocks, Spectra) store text inside
block.attrs rather than innerContent. New opt-in attrs_text_keys parameter recurses into attributes and applies find/replace at matching keys; default off preserves original semantics.
- Real-world transport behavior. A 15.8 MB REST response (yes, really) returns a structured
response_too_large payload with byte counts and correlation id — the agent can now branch on it cleanly instead of treating it as a connection error.
Test suite: 132 tests / 409 assertions.
Writes, scope gates, and transport defenses
The first ability set that mutates content — behind five layers of safety. Introduces a block-aware write path, bulk find/replace, and the L3 / L4 gates that turn "AI can do anything I can do" into something you'd actually trust on a production site.
ppok/set-post-blocks — accepts a parsed block tree, runs the full gate stack (block allowlist, scope, capability), captures a pre-update revision, and writes via serialize_blocks.
ppok/replace-in-content — block-aware bulk find/replace; dry_run defaults true and returns matched posts, total replacements, and preview snippets with context labels before any write lands.
- Scope gates (L4). Writes are limited to specific post types and statuses (default: draft posts only). Promotion to
publish additionally requires edit_published_posts.
- Block allowlist (L3). Registered-only or curated mode; the walker recurses through innerBlocks so nested unknown blocks can't slip past.
- Transport defenses on
rest-call. 1MB response cap, structured response_too_large errors, a try/catch that converts any throwable into a clean WP_Error, and a correlation id stamped onto every response.
- Audit log gets richer. Errors are classified into typed result codes (
response_too_large, unknown_block, scope_blocked, …) so the activity viewer's category filter actually means something. Oversize payloads are stubbed at 256KB instead of bloating the events table.
Test suite: 98 tests / 341 assertions.
Discovery aggregator + DX optimization
A token-efficiency pass on read responses, plus a new aggregated discovery ability so an AI can learn the whole surface in one round trip instead of N+1 calls.
ppok/abilities — bundled discovery returning {name, title, description, input_schema, output_schema, example} per ability in a single call. Collapses discover-abilities + N × get-ability-info to one round trip.
- Noisy fields stripped by default.
yoast_head, yoast_head_json, permalink_template, class_list, generated_slug, and _links are dropped from rest-call responses; include_extended=true keeps them.
- Cursor pagination. GET collection responses with more pages get
has_more=true and a next_cursor that the agent passes straight back in without manually managing page params.
Admin surface
A real settings page and a real activity viewer — the two surfaces a site owner needs to actually trust an AI on their site.
- Settings page on the WordPress Settings API. Toggle individual abilities on, choose retention, configure scope gates (write post types & statuses), and pick block allowlist mode.
- Activity viewer at Tools → Ppok Activity. Filter by ability, result, error code, MCP client, actor, target post, correlation id, or date range. Row-detail screen pretty-prints payloads and renders diffs when present.
Audit foundations
Every audit row now carries the metadata you need to investigate later — correlation id, MCP client, originating IP — populated from a single shared request context.
- Correlation ids on every call. Sourced from
X-Request-ID or X-MCP-Session when present, UUID fallback otherwise.
- MCP client + IP captured alongside the actor on every event.
The first ability: ppok/rest-call
A generic WordPress REST pass-through wrapping rest_do_request() — one ability that exposes the entire REST surface your role can hit, gated by the same capability checks that already protect your site. No bespoke API for the AI to learn; if you can do it in wp-admin, the AI can do it through MCP.