feat(abilities): expose templates tool group via the WordPress Abilities API - #58
Conversation
Writes the failing tests for BLOCK-34 first, before any production code change. Confirmed red against current origin/develop, where TEMPLATE_TOOLS is already wired into scripts/export-abilities-manifest.mjs's buildManifest() (the manifest already carries all 4 templates tools at 32 total / 29 non-yoast -- registration is manifest-driven and doesn't depend on Tool_Executor actually knowing how to run a tool), but two real gaps remain: 1. update_template/reset_template map to the manifest's default 'edit_post' permission instead of a gated one -- Abilities_Registry's permission check never consults Template_Manager::edits_enabled(), so the write gate the REST routes enforce (POST /template, POST /template/reset) doesn't apply on the Abilities/MCP-Adapter path at all. 2. Tool_Executor::execute() has no case for any of the 4 template tool names, so every one of them 400s with "Unknown Block MCP tool" the moment an agent actually tries to call it, gate aside. New tests/Abilities/TemplateAbilitiesTest.php covers both: ability registration presence, list_templates/get_template execution, and the write pair's gate parity with their REST twins -- toggle off denies (editor, subscriber), the gk/block-mcp/templates/allow-edits filter forcing it off despite the stored option, toggle on + edit_posts succeeds and persists (round-tripped via get-template), toggle on + no capability still denies, edit_theme_options alone (the "self" connection path) succeeds, and reset-template's full round trip. Extends AbilitiesRegistryTest's existing readonly/destructive annotation tests to cover the templates group (these already pass -- the annotations came through correctly with the manifest wiring) and adds the four template tool/ability names to the existing manifest-count/ability-ids assertions without changing the counts (both already account for the templates group). tests/abilities-manifest.test.ts gains three assertions: the templates group is present (already true), list_templates/get_template map to 'read' (already true), and update_template/reset_template map to a new 'template_edit' permission distinct from 'edit_post' (not yet true -- genuinely red). Confirmed red: $ vendor/bin/phpunit -c tests/phpunit.xml tests/Abilities/ Tests: 153, Assertions: 360, Failures: 8. (list_templates/get_template/update_template: "Unknown Block MCP tool"; update_template/reset_template gate-off + filter-off tests: got 'unknown_tool' instead of 'ability_invalid_permissions' -- the wrong 'edit_post' permission branch let an editor through, then execution 404'd instead of the permission callback denying it; edit_theme_options-alone test: "does not have necessary permission" -- edit_post's per-post branch doesn't recognize edit_theme_options at all) $ npm test -- tests/abilities-manifest.test.ts Tests 1 failed | 4 passed (5) (update_template/reset_template permission: expected 'template_edit', received 'edit_post') Claude-Session: https://claude.ai/code/session_013YcSbKroBJjPanX3okQrT3
Makes the BLOCK-34 red tests pass. scripts/export-abilities-manifest.mjs: new TEMPLATE_EDIT permission bucket (update_template, reset_template) mapped to a 'template_edit' permission key, checked before the read/edit_post fallbacks in permissionFor() so these two tools stop resolving to the ungated default 'edit_post' branch. Regenerated tools.manifest.json (still 32 tools -- TEMPLATE_TOOLS was already wired into buildManifest()'s tool list; only the two permission values change). Abilities_Registry::check_tool_permission() gets a 'template_edit' case that delegates to REST_Controller::check_template_edit_permissions() -- the exact same method POST /template and POST /template/reset use as their permission_callback -- so the toggle (Template_Manager::edits_enabled()) and its capability half (edit_posts or edit_theme_options) are enforced identically on both surfaces. No gate logic is re-implemented here. Tool_Executor::execute() gains dispatch cases + execute_*() methods for list_templates, get_template, update_template, and reset_template, each delegating to the matching REST_Controller handler via call_controller() (the existing hand-built WP_REST_Request + set_url_params()/set_body_params() pattern every other ability already uses). Claude-Session: https://claude.ai/code/session_013YcSbKroBJjPanX3okQrT3
…cipe A new tool group file needs an explicit import + spread in scripts/export-abilities-manifest.mjs's buildManifest() (it only exports groups it's told about), a permission mapping in permissionFor() (reusing an existing bucket or adding a new gated one, mirroring the REST route's own permission callback -- never re-implementing the gate), and a Tool_Executor::execute() dispatch case -- three steps BLOCK-29/33 skipped for the templates group and BLOCK-34 had to backfill. Claude-Session: https://claude.ai/code/session_013YcSbKroBJjPanX3okQrT3
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 29 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
…_Executor (#59) * test(abilities): list_binding_sources + create_pattern execution/gate parity [red] Writes the failing tests for BLOCK-35 first, before any production code change. Same class of gap Agent A flagged after #57/#55 merged and I fixed for the templates group in #58: both tools are already in the manifest and register as abilities (registration is manifest-driven, independent of Tool_Executor), but neither has a Tool_Executor::execute() case, so both 400 "Unknown Block MCP tool" over the Abilities/MCP-Adapter transport regardless of the caller's permissions. New tests/Abilities/BindingSourcesAbilityTest.php: registration presence, execution returns the {sources:[...]} shape, subscriber denial (read permission parity with the rest of the discovery group), readonly annotation. New tests/Abilities/CreatePatternAbilityTest.php: registration presence, execution creates a real wp_block post for an editor, subscriber denial (base edit_posts check), and the actual gate-parity bug this issue exists to fix — a Contributor (has edit_posts, lacks publish_posts) must be denied by the ability exactly as REST_Controller::check_create_pattern_permissions() denies the identical request over REST, because that permission callback checks wp_block's create_posts capability (-> publish_posts) on top of the base edit_posts check. A manifest permission of plain 'edit_post' would let this actor through the ability while REST denies them. Also pins check_create_pattern_permissions() denying the same Contributor directly, and the create-pattern annotation (not readonly, not destructive). tests/abilities-manifest.test.ts gains two assertions: list_binding_sources already maps to 'read' (confirms no TS-side change needed there — the gap is PHP-execution-only for this tool) and create_pattern must map to a new 'create_pattern' permission distinct from 'edit_post' (genuinely red). Confirmed red: $ vendor/bin/phpunit -c tests/phpunit.xml tests/Abilities/ Tests: 163, Assertions: 403, Failures: 3. (list_binding_sources execution: "Unknown Block MCP tool"; create_pattern execution: "Unknown Block MCP tool"; create_pattern Contributor-denial: got 'unknown_tool' instead of 'ability_invalid_permissions' -- the wrong 'edit_post' permission branch let the Contributor through, then execution 400'd instead of the permission callback denying it) $ npm test -- tests/abilities-manifest.test.ts Tests 1 failed | 6 passed (7) (create_pattern permission: expected 'create_pattern', received 'edit_post'; list_binding_sources permission already correctly 'read') Claude-Session: https://claude.ai/code/session_013YcSbKroBJjPanX3okQrT3 * feat(abilities): wire list_binding_sources + create_pattern execution/gate parity [green] Makes the BLOCK-35 red tests pass. scripts/export-abilities-manifest.mjs: new CREATE_PATTERN permission bucket mapping create_pattern to a 'create_pattern' permission key (checked before the edit_post fallback). list_binding_sources needed no change -- its readOnlyHint annotation already resolved to 'read'. Regenerated tools.manifest.json (still 33 tools; only create_pattern's permission value changes). Abilities_Registry::check_tool_permission() gets a 'create_pattern' case that delegates to REST_Controller::check_create_pattern_permissions() -- the same dedicated callback POST /patterns uses, which checks edit_posts AND wp_block's create_posts capability (publish_posts). No gate logic is re-implemented. Tool_Executor::execute() gains dispatch cases + execute_list_binding_sources() (delegates to REST_Controller::get_binding_sources()) and execute_create_pattern() (delegates to REST_Controller::create_pattern(), passing input through as the JSON body -- Pattern_Manager::create_pattern() already validates title/content-blocks-XOR/sync_status/status, so no duplicate validation belongs here), both via the existing call_controller() pattern. Also fixes a bug in the red commit's own test: create_pattern's response key is `pattern_id`, not `id` (Pattern_Manager::create_pattern()'s actual return shape) -- caught by an "Undefined array key" error on the first green run, not a silent false pass. Claude-Session: https://claude.ai/code/session_013YcSbKroBJjPanX3okQrT3
What changed
Exposes the templates tool group (
list_templates,get_template,update_template,reset_template) via the WordPress Abilities API / MCP Adapter (BLOCK-34) — the follow-up flagged in the BLOCK-29 PR.Starting state on fresh
origin/develop(worth stating plainly, since it changes the shape of this PR):TEMPLATE_TOOLSwas already wired intoscripts/export-abilities-manifest.mjs'sbuildManifest()— the committed manifest already carried all 4 templates tools at 32 tools total / 29 non-yoast ability ids, since ability registration is manifest-driven and doesn't depend onTool_Executoractually knowing how to run a tool. Two real gaps remained:update_template/reset_templateresolved to the manifest's defaultedit_postpermission — the same as every ordinary per-block write — so the Abilities/MCP-Adapter surface could write to a template with none of thegk_block_api_template_editstoggle checking the REST routes (POST /template,POST /template/reset) enforce. An agent connected over the Adapter could bypass the operator's off-by-default consent entirely.Tool_Executor::execute()had nocasefor any of the 4 tool names, so every call 400'd with"Unknown Block MCP tool"regardless of permission.Fix
scripts/export-abilities-manifest.mjs: newTEMPLATE_EDITpermission bucket (update_template,reset_template) → manifest permission'template_edit', checked before theedit_postfallback. Regeneratedtools.manifest.json— still 32 tools, only the two permission values change.Abilities_Registry::check_tool_permission(): new'template_edit'case that callsREST_Controller::check_template_edit_permissions()directly — the exact method the REST routes use as theirpermission_callback. No gate logic is duplicated or re-implemented; both surfaces share one source of truth for the toggle + capability check.Tool_Executor::execute(): dispatch cases +execute_list_templates()/execute_get_template()/execute_update_template()/execute_reset_template(), each delegating to the matchingREST_Controllerhandler via the existingcall_controller()pattern (hand-builtWP_REST_Request+set_url_params()/set_body_params()) every other ability already uses.AGENTS.md: the "Add an MCP tool" recipe gets a 4th step covering the Abilities-wiring gap this issue backfilled — a new tool-group file needs an explicit import in the export script, a permission mapping, and aTool_Executorcase, none of which step 3 (npm run build) alone provides.TDD: red before green (commit history is the proof, not squashed)
393d9dctest(abilities): templates group registration + gate parity [red] — newtests/Abilities/TemplateAbilitiesTest.php(registration presence, read execution, 8 gate-parity scenarios) + 3 new assertions intests/abilities-manifest.test.ts(group presence, read permission,template_editpermission) + minor additions toAbilitiesRegistryTest's existing readonly/destructive-annotation tests. Confirmed genuinely red against unmodifiedorigin/develop:6a58fabfeat(abilities): wire templates group execution + gate parity [green] — the fix described above. All 8 PHP failures + the 1 TS failure go green; nothing else regresses.bc484b3docs(agents): add the Abilities-wiring step to the Add-an-MCP-tool recipe.Acceptance criteria
TEMPLATE_TOOLSreachable through the manifest export andAbilities_Registry/Tool_Executor(already true for the manifest;Tool_Executorexecution is new here).update_template/reset_templateability permission callbacks honorgk_block_api_template_edits+gk/block-mcp/templates/allow-editsexactly as the REST routes do — proven by delegating to the identical method, and by 8 PHPUnit scenarios plus live MCP-transport smoke evidence below (toggle off denies an editor and an admin's own filter-forced-off case; toggle on +edit_postssucceeds and persists; toggle on + no capability still denies a subscriber;edit_theme_optionsalone succeeds — the "self" connection path).AGENTS.md's "Add an MCP tool" recipe updated.Gate output (real numbers, run in the worktree)
Siteminter smoke — real MCP Adapter HTTP transport, not wp-cli fallback
Fresh site
blockmcp-c(WordPress 7.0.2 — Abilities API ships in core; the separate WordPress MCP Adapter plugin isn't bundled, so I installed it from its GitHub Release (v0.5.0) to exercise the real path rather than falling back to directwp_get_ability()->execute()calls). Plugin symlinked to the worktree; both toggles (gk_block_api_abilities_enabled,gk_block_api_template_edits) exercised live.MCP
initializehandshake (real session, realMcp-Session-Id):tools/list— all 4 template tools present among 29 total:Gate parity, toggle OFF →
tools/call update-templatedenied (admin, who has every capability — proves the toggle, not a capability gap, is what's blocking):{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"Editing theme templates is turned off for this site. A site administrator can enable it under Block MCP → Settings."}],"isError":true}}Toggle ON →
tools/call update-templatesucceeds, creates the override:{"jsonrpc":"2.0","id":4,"result":{"structuredContent":{"success":true,"wp_id":4,"override_created":true,"revert_hint":"Call reset_template...","warnings":[],"before_revision_id":0,"revision_id":5},"isError":false}}Verified independently via wp-cli and a real page render:
tools/call get-templateconfirms the read side sees the override too:Capability half of the gate — a subscriber denied even with the toggle ON (proves the toggle widens what a capable actor may do, it doesn't replace the capability check):
{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"You do not have permission to edit templates."}],"isError":true}}tools/call reset-template— deletes the override, full round trip:{"jsonrpc":"2.0","id":6,"result":{"structuredContent":{"success":true,"id":"twentytwentyfive//header","wp_id":4},"isError":false}}Site destroyed after this run; confirmed no leftover containers.
Fixes BLOCK-34
https://linear.app/gravitykit/issue/BLOCK-34/expose-the-templates-tool-group-via-the-wordpress-abilities-adapter
https://claude.ai/code/session_013YcSbKroBJjPanX3okQrT3