=== Ppok AI Toolkit ===
Contributors: ppok
Tags: mcp, ai, model-context-protocol, claude, llm
Requires at least: 6.4
Tested up to: 6.7
Requires PHP: 8.1
Stable tag: 0.4.4
License: GPL-2.0-or-later (plus proprietary components — see LICENSE.md)
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Turn this WordPress site into a Model Context Protocol (MCP) endpoint that AI assistants can connect to via a companion remote bridge.

== Description ==

Ppok AI Toolkit exposes selected WordPress capabilities (REST resources, custom tools, site context) through a Model Context Protocol-compatible interface, so that AI clients such as Claude Desktop can discover and call them through a remote MCP bridge (see Automattic/mcp-wordpress-remote for the reference Node.js bridge).

Features (scaffolded; iterating):

* Settings page for enabling MCP, choosing an auth method, and scoping capabilities.
* REST namespace under `ppok/v1/mcp/*` for discovery and tool invocation.
* Pluggable tool registry for adding custom MCP tools.
* App-password / OAuth-friendly permission checks.

== Licensing ==

This plugin ships under a split license:

* WordPress-integrated code (everything outside `includes/lib/`) — GPL-2.0-or-later.
* Standalone library code in `includes/lib/` — proprietary.

See `LICENSE.md` at the plugin root for the full policy.

== Installation ==

1. Upload the `ppok-ai-toolkit` folder to `/wp-content/plugins/`.
2. Run `composer install --no-dev` inside the plugin folder.
3. Activate the plugin in the WordPress admin.
4. Visit *Settings → AI Toolkit* to configure MCP.

== Changelog ==

= 0.4.4 =
* Phase 14: new read-only ppok/page-outline ability — returns a depth-tracked flat outline of a post's block tree as `[{block_name, depth, brief_data_summary, anchor?}]`. No rendered HTML, no full attrs, no innerContent. Cheap structural reconnaissance (typically under 2KB on a page where get-post-blocks returns 100KB+). brief_data_summary falls back from stripped innerHTML to text-shaped attrs (content / text / title / alt / caption / label) so custom blocks (Gutenbricks, ACF Blocks, Spectra) summarize cleanly. Empty-freeform parser artifacts are filtered out so the outline reflects structure, not whitespace.
* View Details modal: new Update_Channel hooks `plugins_api` for the plugin's own slug, fetching https://ppok-ai.com/plugins/ai-toolkit/info.json and translating the info-shape into WP's plugin_information stdClass. Cached in a 6-hour site transient (1-minute negative cache on failure) so opening the modal doesn't round-trip on every click. URL overridable via the `ppok_ai_toolkit_info_json_url` filter.

= 0.4.3 =
* Self-hosted updates: added `Update URI: https://ppok-ai.com/plugins/ai-toolkit/update.json` header. WP 5.8+ delegates this slug's upgrade check to the `update_plugins_ppok-ai.com` hostname-scoped filter (handled hosting-side), bypassing WordPress.org entirely.
* Phase 17(a): ppok/set-post-blocks gains `validate_only` (default false). When true, the call runs L3 block-allowlist + L4 scope + a serialize_blocks/parse_blocks round-trip and returns `{validate_only, bytes_written_would_be, unknown_blocks, schema_violations, roundtrip_drift_blocks}` — diagnostics collected without halting on the first failure and without calling wp_update_post or wp_save_post_revision. Lets agents verify authoring intent against the live WP environment before committing to a draft.
* Phase 17(a): ppok/replace-in-content `dry_run` (already default true) now also predicts the per-candidate apply-path verdict and surfaces it as a `would_skip` field on each `changes[]` entry (`permission_denied` | `scope_blocked`). The preview is now honest about which posts would actually change on apply. Intentionally no L3 here — existing posts may legitimately contain unregistered blocks.
* Phase 17(b): ppok/rest-call accepts `params.validate_only=true` on POST/PUT/PATCH. By default it short-circuits with a structured `validate_only_not_supported` WP_Error (status 501) since no WP core REST endpoint honors the flag. Hook the new `ppok_ai_toolkit_validate_only_routes` filter to allowlist custom endpoints that do (exact match or trailing `*` wildcard prefix). On GET/DELETE the flag is silently dropped — there is nothing to dry-run on a read. For WP core authoring today, create with `status: "draft"` then inspect, or use ppok/set-post-blocks with `validate_only=true` for block-tree authoring.

= 0.4.2 =
* Release tooling: `composer build` now packages a distributable zip into the directory specified by `.build.json` (gitignored). Slims `vendor/` via `composer install --no-dev --optimize-autoloader`, stages a clean copy excluding dev artifacts, writes `ppok-ai-toolkit-{version}.zip`, then restores fat dev deps via try/finally. Cross-checks plugin-header `Version`, `PPOK_AI_TOOLKIT_VERSION` constant, and `readme.txt` `Stable tag` and aborts on mismatch or if the target zip already exists. No runtime plugin changes.

= 0.4.1 =
* Phase 13a (bug fix): ppok/get-post-blocks permission denials now return a structured WP_Error with code `ppok_cap_denied` carrying data.failed_gate / required_cap / target_post_id / target_post_type, instead of a bare "Permission denied" via permission_callback. The per-post edit_post cap check moved inside execute_callback to match ppok/rest-call's gating model (logged-in at the ability layer; per-post cap enforced after the post is loaded). Agents can now self-recover.
* Phase 13b: ppok/blocks-registered gains an `include_schema` parameter (default true). Set false to omit `attributes_schema` from each row — catalog enumeration drops from ~87KB to ~10KB on typical installs.
* Phase 13c: ppok/blocks-find-usage description now explicitly documents the default `post_status=['publish','pending']` so agents widen the net for draft-reconnaissance instead of getting silent zero-hit responses on draft-only matches.
* Phase 13d: ppok/rest-call accepts `route` as a synonym for `path` (both top-level args). Either is fine; if both are provided, `path` wins. Missing both returns a structured `rest_invalid_param` error.
* Phase 13e: ppok/rest-call honors `params.slim_response_keys` — an array of top-level keys to drop from each row after the default strip pass. Lets agents shrink responses on endpoints that don't honor `_fields`.
* Phase 13f: ppok/rest-call strips server-rendered shapes (content.rendered, title.rendered, excerpt.rendered, guid.rendered) from POST/PUT/PATCH responses by default. The caller just authored the content — echoing back 100-200KB of server-rendered HTML is pure waste. `params.include_extended=true` preserves them.
* Phase 13g: ppok/list-posts gains `orderby` (`date` | `modified` | `title` | `menu_order`) + `order` (`ASC` | `DESC`) params, with a stable secondary sort by ID to break ties. DEFAULT flipped from `orderby=date DESC` to `orderby=modified DESC` — agents looking for "current voice" overwhelmingly want most-recently-touched.
* Phase 13i: ppok/blocks-registered response gains a `namespaces:[{name, count}]` summary derived from grouping the catalog by slash-prefix. Lets agents preview the block ecosystem cheaply before pulling rows.
* Phase 13j: documented in the ppok/rest-call description that WP REST's native `params._fields` is already honored via pass-through (no plugin code change needed) — verified working on dotfoundry where slim `_fields` responses meant the agent never had to dump to a file. The example() payload now demonstrates `_fields`.
* Phase 20: maintenance — split `.context/project.json` into a tree of focused Markdown files (README + principles + plugin + licensing + architecture + sftp + git + structure + build + upstream-findings + completed + phase-evolution + todo-policy + features + enhancements). Editability + GitHub rendering + grep all improve; the single ~74KB file had grown past comfortable JSON-editing thresholds. CLAUDE.md updated to point at the new `.context/README.md`; five PHP doc-comments updated to cite the new file locations.

= 0.4.0 =
* Phase 5: new read-only ppok/blocks-registered ability — dumps WP_Block_Type_Registry as {name, title, description, category, is_dynamic, attributes_schema}. Optional namespace-prefix filter.
* Phase 5: new read-only ppok/blocks-find-usage ability — given a canonical block_name, returns posts containing it with per-post counts. SQL LIKE prefilter + parse_blocks recursive walk so nested usage counts and innerHTML false-positives don't. Core-namespace shortform alias handled.
* Phase 5: new read-only ppok/patterns-registered ability — dumps WP_Block_Patterns_Registry (core + theme + plugin-registered) with full content; optional category filter; include_content=false enumerates the catalog cheaply.
* Discovery hygiene: Abilities_List aggregator now also surfaces the example() payload for ppok/preview-url (Phase-4 omission).

= 0.3.0 =
* Phase 4: new ppok/preview-url ability — mints a short-lived (5-minute) HMAC-signed URL that bypasses the draft/pending/private redirect for visual review without admin login. Write-flagged so every mint is audited and admin opt-in via abilities_enabled is required. Companion pre_get_posts handler expands post_status when a valid token is on the request.

= 0.2.1 =
* Phase 3: rest-call slim default response (strips yoast_head, yoast_head_json, permalink_template, class_list, generated_slug) — pass params.include_extended=true to keep them.
* Phase 3: rest-call cursor pagination — collection responses with more pages return has_more + opaque next_cursor; pass back as params.cursor.
* Phase 3: new ppok/abilities aggregator — one call returns {name, title, description, input_schema, output_schema, example} for every registered ability.
* Phase 6 partial: new write ability ppok/set-post-blocks — accepts a parsed block tree, runs serialize_blocks + wp_update_post, returns revision_id for one-call rollback. Gated by L1 cap + L3 block-allowlist + L4 plugin-options scope (write_post_types, write_post_statuses).
* Phase 6 partial: new write ability ppok/replace-in-content — block-aware bulk find/replace; dry_run defaults true; capped at 500 candidate posts per call; opt-in attrs_text_keys parameter to traverse block attrs (Gutenbricks, ACF Blocks).
* Transport defenses: rest-call returns structured response_too_large WP_Error (default 1MB cap, filterable) instead of letting the bridge drop with "Connection closed"; try/catch around dispatch surfaces PHP exceptions as rest_call_dispatch_exception; correlation_id stamped onto every WP_Error data array; Event_Logger caps payload at 256KB with a stub.
* Bug fix: replace-in-content schema now uses anyOf for post_type/post_status (oneOf mis-validated valid string input on WP REST).

= 0.2.0 =
* Wire Request_Context at rest_pre_dispatch so audit rows carry correlation_id, mcp_client, and ip (previously NULL on every row).
* Fix Activator option seeds to match the v1 settings keys (abilities_enabled, write_post_types, write_post_statuses, block_allowlist_mode, block_curated_allowlist, log_retention_days).
* Settings page rebuilt on the WP Settings API; abilities_enabled toggles meta.mcp.public per write ability at registration.
* New Tools → Ppok Activity viewer over wp_ppok_ai_events with filters, free-text reason search, and a row-detail screen.

= 0.1.0 =
* Initial scaffold.
