Skip to content

feat(patterns): create_pattern — synced-pattern creation with sync-status control - #57

Merged
zackkatz merged 7 commits into
developfrom
feature/block-31-create_pattern-synced-pattern-creation-with-sync-status
Jul 22, 2026
Merged

feat(patterns): create_pattern — synced-pattern creation with sync-status control#57
zackkatz merged 7 commits into
developfrom
feature/block-31-create_pattern-synced-pattern-creation-with-sync-status

Conversation

@zackkatz

@zackkatz zackkatz commented Jul 22, 2026

Copy link
Copy Markdown
Member

What changed

New create_pattern tool creates a synced pattern (a wp_block post) with sync-status control — the first-class path for "turn this section into a reusable pattern."

  • Pattern_Manager::create_pattern( array $args ) (wordpress-plugin/gk-block-mcp/includes/class-pattern-manager.php): validates title and the content/blocks XOR; structured blocks go through Block_CRUD::build_block_from_def() per top-level block — the same registry/tier/dual-storage validation create_post uses; raw content through wp_kses_post(). wp_insert_post() with post_type => 'wp_block'. sync_status: "unsynced" sets core's own wp_pattern_sync_status post meta; "synced" (default) leaves it absent. Response: {pattern_id, title, slug, sync_status, edit_url, reference: {blockName:'core/block', attrs:{ref}}, warnings}.
  • format_synced_pattern() now reports sync_status on every synced pattern (existing GET /patterns responses gain the field too).
  • Pattern_Manager's constructor now takes a Block_CRUD; gk-block-mcp.php's service-graph construction is reordered so block_crud builds before pattern_manager.
  • REST: POST /patterns (WP_REST_Server::CREATABLE, same route as the existing GET), gated by a new check_create_pattern_permissions()edit_posts (base) + wp_block's create_posts capability (maps to publish_posts, which the agent role holds).
  • TS: src/client.ts createPattern(); src/tools/patterns.ts new create_pattern tool in PATTERN_TOOLS (schema: title required, blocks/content XOR via BLOCK_INPUT_SCHEMA from write.js, sync_status enum default synced, slug?, status? enum default publish); handler validates the XOR client-side and defaults sync_status/status before calling the client (mirrors insert_pattern's client-side synced default). New CreatePatternRequest/CreatePatternResponse types; Pattern gains optional sync_status.
  • Docs: README row, one agent-guide sentence ("extract repeated sections into a pattern, then reference it"). Regenerated tools.manifest.json (33 tools) + assets/mcp-server/index.cjs.
  • Out of scope, not built (per spec): pattern update, pattern delete, post-hoc sync-status flips.

Strict TDD — commit sequence is the proof

Per an explicit requirement for this issue, tests were written and committed FIRST, confirmed red, then implemented to green in later commits — see the commit list on this PR:

  1. test(patterns): create_pattern sync-status + XOR + cap checks [red] — PHP, 7 tests, all erroring "Call to undefined method" before implementation
  2. test(patterns): create_pattern XOR validation + dispatch + response passthrough [red] — TS, 8 tests, all failing "Unknown pattern tool: create_pattern"
  3. feat(patterns): implement create_pattern [green] — PHP implementation, same 7 tests now pass
  4. test(patterns): update Pattern_Manager call sites for the new Block_CRUD param — fixture updates for the constructor signature change
  5. feat(patterns): create_pattern MCP tool [green] — TS implementation, same 8 tests now pass
  6. test(patterns): bump tool-count fixtures to 33 for create_pattern
  7. docs(patterns): README row + agent-guide sentence; regenerate manifest + bundle

No local squashing — this sequence is pushed as-is.

Acceptance criteria

  • PHP: synced → meta absent (get_post_meta(...,'wp_pattern_sync_status',true) === '')
  • PHP: unsynced → meta set to 'unsynced'
  • PHP: created pattern appears in GET /patterns with correct sync_status
  • PHP: legacy-tier block rejected (legacy_block error code)
  • PHP: XOR violation (both or neither) → 400
  • PHP: missing cap (contributor, no publish_posts) → 403
  • TS: XOR validation (title required; content/blocks exactly one)
  • TS: dispatch (defaults, forwarding of sync_status/slug/status/blocks)
  • TS: response passthrough incl. reference
  • Manifest + bundle regenerated; README + agent-guide updated; full gate green

Gate output

npm test        → Test Files 55 passed (55) · Tests 854 passed (854)
npm run build    → dist/index.cjs 3.6mb, bundle copied to wordpress-plugin/gk-block-mcp/assets/mcp-server/index.cjs
node scripts/export-abilities-manifest.mjs && git diff --exit-code tools.manifest.json → clean, no drift
composer test    → OK (1394 tests, 17821 assertions) · OK (28 tests, 82 assertions) [yoast] · OK (11 tests, 25 assertions) [adapter] · OK (5 tests, 37 assertions) [multisite]
composer lint    → 0 errors, 0 warnings
composer analyze → [OK] No errors

Siteminter smoke

Site blockmcp-a (Docker/Siteminter, http://localhost:8960, gk-block-mcp mounted from this worktree), real HTTP requests with WP Application Passwords, against the actual merged code:

1. POST /patterns {title, content} (no sync_status)
   → {"pattern_id":4,"sync_status":"synced",...,"reference":{"blockName":"core/block","attrs":{"ref":4}}}

2. POST /patterns {title, content, sync_status:"unsynced"}
   → {"pattern_id":5,"sync_status":"unsynced",...}

3. GET /patterns?limit=100&synced=true → both patterns present with correct sync_status:
   4 Smoke Test Synced Pattern synced
   5 Smoke Test Unsynced Pattern unsynced

4. POST /patterns {title} (neither content nor blocks) → HTTP 400
5. POST /patterns {title, content, blocks} (both)      → HTTP 400
6. POST /patterns as a contributor (no publish_posts)  → HTTP 403
7. POST /patterns {title, blocks:[heading, paragraph]} → 201, and GET /patterns/6 .preview_html shows
   correctly serialized <!-- wp:heading {"level":2} --><h2>Hi</h2>... <!-- wp:paragraph -->...

Test fixtures (posts 4/5/6, the contributor user) were deleted after verification.

Fixes BLOCK-31
https://linear.app/gravitykit/issue/BLOCK-31

https://claude.ai/code/session_013YcSbKroBJjPanX3okQrT3

Summary by CodeRabbit

  • New Features

    • Added a create_pattern tool for creating reusable WordPress patterns from structured blocks or raw content.
    • Supports synced or unsynced patterns, custom slugs, draft or published status, and returns pattern references and edit links.
    • Added REST API support with validation and permission checks.
    • Updated agent guidance to encourage reusable synced patterns.
  • Documentation

    • Documented the new pattern creation tool.
  • Tests

    • Added coverage for validation, permissions, sync status, block handling, and API responses.

💾 Build file (d593847).

@linear-code

linear-code Bot commented Jul 22, 2026

Copy link
Copy Markdown

BLOCK-31

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR adds create_pattern support across MCP, REST, and WordPress pattern services. It supports structured blocks or raw content, synced or unsynced status, publication options, capability checks, pattern references, and integration coverage.

Changes

Pattern creation

Layer / File(s) Summary
MCP contracts and dispatch
src/types.ts, src/client.ts, src/tools/patterns.ts, wordpress-plugin/gk-block-mcp/assets/mcp-server/index.cjs, src/__tests__/*, src/agent-guide.ts
Defines the create-pattern request and response shapes, registers the MCP tool, validates inputs, applies defaults, dispatches REST requests, and tests response passthrough.
Pattern creation and sync state
wordpress-plugin/gk-block-mcp/includes/class-pattern-manager.php, wordpress-plugin/gk-block-mcp/gk-block-mcp.php
Adds Block CRUD integration, sync-status metadata, content sanitization and block construction, post insertion, references, warnings, and service wiring.
REST and ability registration
wordpress-plugin/gk-block-mcp/includes/class-rest-controller.php, wordpress-plugin/gk-block-mcp/includes/abilities/tools.manifest.json, wordpress-plugin/gk-block-mcp/tests/Abilities/*
Adds the POST /patterns endpoint, creation capability checks, the ability manifest entry, and updated registry counts.
Integration validation
wordpress-plugin/gk-block-mcp/tests/REST/*, wordpress-plugin/gk-block-mcp/tests/Block/*, wordpress-plugin/gk-block-mcp/tests/Stress/*
Tests sync metadata, pattern discovery, validation failures, legacy-block rejection, permissions, and updated Pattern_Manager dependency fixtures.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MCPTool
  participant WordPressBlockClient
  participant RESTController
  participant PatternManager
  participant WordPress
  MCPTool->>WordPressBlockClient: createPattern(request)
  WordPressBlockClient->>RESTController: POST /patterns
  RESTController->>PatternManager: create_pattern(args)
  PatternManager->>WordPress: create wp_block and sync metadata
  WordPress-->>PatternManager: pattern identifiers and content
  PatternManager-->>RESTController: reference and warnings
  RESTController-->>WordPressBlockClient: created pattern response
  WordPressBlockClient-->>MCPTool: pattern result
Loading

Possibly related PRs

  • GravityKit/block-mcp#50: Uses the manifest-driven ability registration and validation mechanisms updated by this PR.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the new create_pattern feature and its sync-status control, matching the main change in the PR.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/block-31-create_pattern-synced-pattern-creation-with-sync-status

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (5)
src/types.ts (1)

473-481: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Encode the content/blocks XOR in the public type.

The interface permits both fields or neither, despite documenting “exactly one.” Prefer a union with one required branch and the other never; retain runtime validation for untyped callers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/types.ts` around lines 473 - 481, Update CreatePatternRequest to a
discriminated union with one branch requiring content and forbidding blocks via
never, and another requiring blocks and forbidding content via never; preserve
the existing shared fields and runtime validation for untyped callers.
src/__tests__/tools/patterns/create_pattern.test.ts (1)

1-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Docblock claims "unknown tool still throws" coverage that isn't in this file.

No test in this suite dispatches an unrecognized tool name through handlePatternTool. Either add the regression test or trim the claim from the docblock.

✅ Example regression test to add
it('throws for an unknown tool', async () => {
  await expect(
    handlePatternTool('not_a_real_tool' as any, {}, client as any)
  ).rejects.toThrow();
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/__tests__/tools/patterns/create_pattern.test.ts` around lines 1 - 11,
Update the create_pattern test docblock to remove the “Unknown tool still
throws” coverage claim, since this suite does not currently dispatch an
unrecognized tool through handlePatternTool. Keep the remaining coverage
descriptions unchanged.
wordpress-plugin/gk-block-mcp/tests/REST/CreatePatternTest.php (1)

121-141: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider covering the remaining validation branches.

invalid_sync_status, invalid_status, block_depth_exceeded, and invalid_block are new dedicated error codes in Pattern_Manager::create_pattern() with no direct test here (only the XOR and legacy-tier branches are covered).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wordpress-plugin/gk-block-mcp/tests/REST/CreatePatternTest.php` around lines
121 - 141, Extend the CreatePatternTest coverage with direct cases for
Pattern_Manager::create_pattern() returning each new error code:
invalid_sync_status, invalid_status, block_depth_exceeded, and invalid_block.
Add assertions that each response is a WP_Error with the expected dedicated
code, while preserving the existing XOR and legacy-tier tests.
wordpress-plugin/gk-block-mcp/includes/class-pattern-manager.php (1)

745-754: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Inline compound condition — extract to a named variable for consistency.

Two lines later, the content/blocks XOR check is correctly assigned to named $has_blocks/$has_content before the if. The title check inlines a two-predicate compound expression directly instead.

As per coding guidelines, wordpress-plugin/gk-block-mcp/**/*.php: "Assign checks to named variables before if, while, or ternary conditionals instead of inlining function calls or compound expressions, except load-bearing short-circuit guards."

♻️ Suggested fix
 		$title = isset( $args['title'] ) ? $args['title'] : null;
-		if ( ! is_string( $title ) || '' === $title ) {
+		$title_is_invalid = ! is_string( $title ) || '' === $title;
+		if ( $title_is_invalid ) {
 			return new \WP_Error(
 				'missing_title',
 				__( 'A non-empty "title" is required.', 'gk-block-mcp' ),
 				array( 'status' => 400 )
 			);
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wordpress-plugin/gk-block-mcp/includes/class-pattern-manager.php` around
lines 745 - 754, In PatternManager::create_pattern, extract the title validation
expression into a named boolean variable before the conditional, then have the
if check that variable. Preserve the existing behavior: non-string titles and
the empty string, including title "0" as valid, must return the same WP_Error.

Source: Coding guidelines

wordpress-plugin/gk-block-mcp/tests/Abilities/AbilitiesRegistryTest.php (1)

61-65: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the new tool name, not only the count.

The count change can remain green if create_pattern is omitted and another tool replaces it. Add assertContains( 'create_pattern', $names ) to verify the new registration directly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wordpress-plugin/gk-block-mcp/tests/Abilities/AbilitiesRegistryTest.php`
around lines 61 - 65, Add a direct assertion for create_pattern to the tool-name
checks in AbilitiesRegistryTest, alongside the existing get_page_blocks,
edit_block_tree, and site_editor_context assertions. Keep the manifest count
assertion unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/client.ts`:
- Around line 397-404: Add the required `@since` 2.1.0 annotation to the JSDoc for
the new public pattern-creation method, alongside its existing documentation
tags.
- Around line 409-412: The client and MCP layers must use identical validation
and normalization for create_pattern inputs. In src/client.ts lines 409-412,
ensure empty blocks are not posted alongside valid content and reject inputs
that do not contain exactly one non-empty value; apply the same presence and
validity rules in src/tools/patterns.ts lines 103-106 before the REST call,
preserving acceptance of either valid content or non-empty blocks but never
both.

In `@src/tools/patterns.ts`:
- Around line 23-49: Update the input schema in the pattern tool to enforce the
runtime contract: require a non-blank title, require exactly one of content or
blocks, reject empty content, and require blocks to contain at least one item.
Use the schema’s supported conditional and minimum constraints while preserving
the existing BLOCK_INPUT_SCHEMA validation and optional fields.
- Around line 109-115: Validate the optional sync_status and status values
against their allowed enums, and validate slug is a string when provided, before
the client.createPattern call in the pattern handler. Reject invalid arguments
before constructing or delegating the request, then retain the existing defaults
and pass only validated values instead of relying on type assertions.

In `@wordpress-plugin/gk-block-mcp/assets/mcp-server/index.cjs`:
- Around line 52805-52830: Update the inputSchema for the pattern tool to encode
validation constraints: require a non-whitespace title, and add oneOf
alternatives so exactly one of content or blocks is provided. Set content to
minLength 1, blocks to minItems 1, and preserve their existing property
definitions and descriptions.
- Around line 40266-40273: The documentation for the pattern-creation operation
incorrectly implies all patterns are synced. Update the source docblocks for
this operation, including the additional description referenced near the related
symbol, to state that it creates patterns with configurable synced or unsynced
status while preserving the existing parameter and return documentation; then
regenerate the bundled index.cjs output.
- Around line 40274-40282: Update createPattern to validate that data is a
non-null object before accessing its properties, then explicitly require
data.title to be a string before trimming or checking emptiness. Preserve the
existing validation errors for missing or invalid titles and the exactly-one-of
content/blocks rule.

In `@wordpress-plugin/gk-block-mcp/includes/abilities/tools.manifest.json`:
- Line 1192: Update the create_pattern ability permission flow: add a
create_pattern case in Abilities_Registry::check_tool_permission() that uses
check_create_pattern_permissions() and enforces the wp_block create_posts
capability, then change the manifest permission from edit_post to create_pattern
and add a contributor regression test covering the required authorization
behavior.

In `@wordpress-plugin/gk-block-mcp/includes/class-rest-controller.php`:
- Around line 1492-1499: Add an `@since` 2.2.0 annotation to the docblock for the
public REST handler create_pattern(), matching the version used by its sibling
methods and the current release context.

---

Nitpick comments:
In `@src/__tests__/tools/patterns/create_pattern.test.ts`:
- Around line 1-11: Update the create_pattern test docblock to remove the
“Unknown tool still throws” coverage claim, since this suite does not currently
dispatch an unrecognized tool through handlePatternTool. Keep the remaining
coverage descriptions unchanged.

In `@src/types.ts`:
- Around line 473-481: Update CreatePatternRequest to a discriminated union with
one branch requiring content and forbidding blocks via never, and another
requiring blocks and forbidding content via never; preserve the existing shared
fields and runtime validation for untyped callers.

In `@wordpress-plugin/gk-block-mcp/includes/class-pattern-manager.php`:
- Around line 745-754: In PatternManager::create_pattern, extract the title
validation expression into a named boolean variable before the conditional, then
have the if check that variable. Preserve the existing behavior: non-string
titles and the empty string, including title "0" as valid, must return the same
WP_Error.

In `@wordpress-plugin/gk-block-mcp/tests/Abilities/AbilitiesRegistryTest.php`:
- Around line 61-65: Add a direct assertion for create_pattern to the tool-name
checks in AbilitiesRegistryTest, alongside the existing get_page_blocks,
edit_block_tree, and site_editor_context assertions. Keep the manifest count
assertion unchanged.

In `@wordpress-plugin/gk-block-mcp/tests/REST/CreatePatternTest.php`:
- Around line 121-141: Extend the CreatePatternTest coverage with direct cases
for Pattern_Manager::create_pattern() returning each new error code:
invalid_sync_status, invalid_status, block_depth_exceeded, and invalid_block.
Add assertions that each response is a WP_Error with the expected dedicated
code, while preserving the existing XOR and legacy-tier tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b31bf996-ff70-4680-9332-1950441b3931

📥 Commits

Reviewing files that changed from the base of the PR and between 0b6db23 and 0caf4dc.

📒 Files selected for processing (19)
  • README.md
  • src/__tests__/helpers/mock-client.ts
  • src/__tests__/tools/patterns/create_pattern.test.ts
  • src/agent-guide.ts
  • src/client.ts
  • src/tools/patterns.ts
  • src/types.ts
  • wordpress-plugin/gk-block-mcp/assets/mcp-server/index.cjs
  • wordpress-plugin/gk-block-mcp/gk-block-mcp.php
  • wordpress-plugin/gk-block-mcp/includes/abilities/tools.manifest.json
  • wordpress-plugin/gk-block-mcp/includes/class-pattern-manager.php
  • wordpress-plugin/gk-block-mcp/includes/class-rest-controller.php
  • wordpress-plugin/gk-block-mcp/tests/Abilities/AbilitiesRegistryTest.php
  • wordpress-plugin/gk-block-mcp/tests/Block/PatternReferenceCountsTest.php
  • wordpress-plugin/gk-block-mcp/tests/REST/CreatePatternTest.php
  • wordpress-plugin/gk-block-mcp/tests/REST/RestSummaryTest.php
  • wordpress-plugin/gk-block-mcp/tests/REST/WriteHandlerErrorEnvelopeTest.php
  • wordpress-plugin/gk-block-mcp/tests/RestControllerTestCase.php
  • wordpress-plugin/gk-block-mcp/tests/Stress/PatternRecursionStressTest.php

Comment thread src/client.ts
Comment on lines +397 to +404
/**
* Create a synced pattern (a `wp_block` post). Exactly one of
* `content`/`blocks` is required; structured `blocks` go through the same
* registry/tier/dual-storage validation as `create_post`.
*
* @param data - Pattern title plus exactly one of content/blocks, sync_status, slug, status
* @returns The created pattern's id, slug, sync_status, edit_url, and a ready-to-insert `reference` snippet
*/

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the required @since annotation.

This new public method should include @since 2.1.0 until the release version is known.

As per coding guidelines, new public production methods require @since {version} annotations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client.ts` around lines 397 - 404, Add the required `@since` 2.1.0
annotation to the JSDoc for the new public pattern-creation method, alongside
its existing documentation tags.

Source: Coding guidelines

Comment thread src/client.ts
Comment thread src/tools/patterns.ts
Comment thread src/tools/patterns.ts
Comment thread wordpress-plugin/gk-block-mcp/assets/mcp-server/index.cjs
Comment on lines +40274 to +40282
async createPattern(data) {
if (!data.title || data.title.trim() === "") {
throw new Error('create_pattern: a non-empty "title" is required');
}
const hasContent = typeof data.content === "string" && data.content !== "";
const hasBlocks = Array.isArray(data.blocks) && data.blocks.length > 0;
if (hasContent === hasBlocks) {
throw new Error('create_pattern: provide exactly one of "content" or "blocks"');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard the client method against malformed input.

A missing data object or a truthy non-string data.title causes a TypeError before the intended validation error. Validate the object and title type explicitly:

Proposed fix
-    if (!data.title || data.title.trim() === "") {
+    if (!data || typeof data.title !== "string" || data.title.trim() === "") {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wordpress-plugin/gk-block-mcp/assets/mcp-server/index.cjs` around lines 40274
- 40282, Update createPattern to validate that data is a non-null object before
accessing its properties, then explicitly require data.title to be a string
before trimming or checking emptiness. Preserve the existing validation errors
for missing or invalid titles and the exactly-one-of content/blocks rule.

Comment thread wordpress-plugin/gk-block-mcp/assets/mcp-server/index.cjs
"output_schema": {
"type": "object"
},
"permission": "edit_post",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C 8 "check_tool_permission|permissionFor|check_create_pattern_permissions|create_pattern" \
  wordpress-plugin/gk-block-mcp/includes \
  wordpress-plugin/gk-block-mcp/tests

Repository: GravityKit/block-mcp

Length of output: 39931


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the manifest entry and the permission switch used by the abilities registry.
sed -n '1180,1210p' wordpress-plugin/gk-block-mcp/includes/abilities/tools.manifest.json
printf '\n---\n'
sed -n '263,340p' wordpress-plugin/gk-block-mcp/includes/class-abilities-registry.php
printf '\n---\n'
sed -n '1,220p' wordpress-plugin/gk-block-mcp/includes/class-pattern-controller.php

Repository: GravityKit/block-mcp

Length of output: 3685


🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -a "class-.*pattern.*\.php|.*pattern.*controller.*\.php|.*pattern.*\.php" wordpress-plugin/gk-block-mcp/includes

Repository: GravityKit/block-mcp

Length of output: 242


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 "function check_edit_permissions|check_edit_permissions\(" wordpress-plugin/gk-block-mcp/includes

Repository: GravityKit/block-mcp

Length of output: 1077


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 "check_edit_permissions|check_create_permissions|create_pattern" wordpress-plugin/gk-block-mcp/includes

Repository: GravityKit/block-mcp

Length of output: 24706


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1136,1165p' wordpress-plugin/gk-block-mcp/includes/class-rest-controller.php

Repository: GravityKit/block-mcp

Length of output: 1109


Route create_pattern through the dedicated create-pattern permission. The manifest still gates this ability with edit_post, but POST /patterns requires check_create_pattern_permissions() and the wp_block create_posts cap. Add a create_pattern case in Abilities_Registry::check_tool_permission() and a contributor regression test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wordpress-plugin/gk-block-mcp/includes/abilities/tools.manifest.json` at line
1192, Update the create_pattern ability permission flow: add a create_pattern
case in Abilities_Registry::check_tool_permission() that uses
check_create_pattern_permissions() and enforces the wp_block create_posts
capability, then change the manifest permission from edit_post to create_pattern
and add a contributor regression test covering the required authorization
behavior.

Comment on lines +1492 to +1499
/**
* POST /patterns — create a synced pattern (a `wp_block` post).
*
* @param \WP_REST_Request $request Request object.
*
* @return \WP_REST_Response|\WP_Error
*/
public function create_pattern( $request ) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Missing @since annotation on the new public REST handler.

The sibling methods added in this same PR (check_create_pattern_permissions(), Pattern_Manager::create_pattern()) both carry @since 2.2.0; this new public handler doesn't.

As per coding guidelines, wordpress-plugin/gk-block-mcp/**/*.php: "Add @since {version} annotations to shipped public production classes, methods, hooks, and REST routes; new code uses @since 2.1.0 until the release version is known."

📝 Suggested fix
 	/**
 	 * POST /patterns — create a synced pattern (a `wp_block` post).
 	 *
+	 * `@since` 2.2.0
+	 *
 	 * `@param` \WP_REST_Request $request Request object.
 	 *
 	 * `@return` \WP_REST_Response|\WP_Error
 	 */
 	public function create_pattern( $request ) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* POST /patterns — create a synced pattern (a `wp_block` post).
*
* @param \WP_REST_Request $request Request object.
*
* @return \WP_REST_Response|\WP_Error
*/
public function create_pattern( $request ) {
/**
* POST /patterns — create a synced pattern (a `wp_block` post).
*
* `@since` 2.2.0
*
* `@param` \WP_REST_Request $request Request object.
*
* `@return` \WP_REST_Response|\WP_Error
*/
public function create_pattern( $request ) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wordpress-plugin/gk-block-mcp/includes/class-rest-controller.php` around
lines 1492 - 1499, Add an `@since` 2.2.0 annotation to the docblock for the public
REST handler create_pattern(), matching the version used by its sibling methods
and the current release context.

Source: Coding guidelines

zackkatz added 7 commits July 22, 2026 19:39
Failing-first PHPUnit coverage for POST /patterns (create_pattern),
written against the not-yet-implemented REST_Controller::create_pattern()
and check_create_pattern_permissions(). All 7 assertions currently
error with "Call to undefined method" — confirmed red before
implementation lands in the next commit(s).

Claude-Session: https://claude.ai/code/session_013YcSbKroBJjPanX3okQrT3
…assthrough [red]

Failing-first Vitest coverage for the not-yet-implemented create_pattern
tool (handlePatternTool has no case for it yet). All 8 assertions
currently fail with "Unknown pattern tool: create_pattern" — confirmed
red before implementation lands in the next commit(s). Adds a
createPattern mock stub to the shared mock client as test scaffolding.

Claude-Session: https://claude.ai/code/session_013YcSbKroBJjPanX3okQrT3
Pattern_Manager::create_pattern() creates a wp_block post: structured
blocks go through Block_CRUD::build_block_from_def() (the same
registry/tier/dual-storage validation create_post uses), raw content
through wp_kses_post(). sync_status defaults to synced (meta absent);
'unsynced' sets core's own wp_pattern_sync_status meta key.
format_synced_pattern() now reports sync_status too.

POST /patterns registers CREATABLE alongside the existing GET, gated
by check_create_pattern_permissions() (edit_posts + the wp_block post
type's create_posts cap, which maps to publish_posts).

Pattern_Manager's constructor now takes a Block_CRUD; gk-block-mcp.php
reorders construction so block_crud is built before pattern_manager.

CreatePatternTest and the create_pattern Vitest suite (committed
red in prior commits) now pass.

Claude-Session: https://claude.ai/code/session_013YcSbKroBJjPanX3okQrT3
client.createPattern() → POST /patterns; new create_pattern tool in
PATTERN_TOOLS validates title + the content/blocks XOR client-side
(mirroring create_post), defaults sync_status to synced and status to
publish, and forwards structured blocks via the shared BLOCK_INPUT_SCHEMA.
New CreatePatternRequest/CreatePatternResponse types; Pattern gains an
optional sync_status field.

The create_pattern Vitest suite (committed red in a prior commit) now
passes.

Claude-Session: https://claude.ai/code/session_013YcSbKroBJjPanX3okQrT3
@zackkatz
zackkatz force-pushed the feature/block-31-create_pattern-synced-pattern-creation-with-sync-status branch from 0caf4dc to d593847 Compare July 22, 2026 23:42
@zackkatz
zackkatz merged commit 8f1a33f into develop Jul 22, 2026
9 checks passed
@zackkatz
zackkatz deleted the feature/block-31-create_pattern-synced-pattern-creation-with-sync-status branch July 22, 2026 23:45
zackkatz added a commit that referenced this pull request Jul 23, 2026
…_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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant