Skip to content

fix: align destructive gates across tool planes - #13

Open
mechkw wants to merge 2 commits into
GravityKit:developfrom
mechkw:feat/consistent-destructive-guardrails
Open

fix: align destructive gates across tool planes#13
mechkw wants to merge 2 commits into
GravityKit:developfrom
mechkw:feat/consistent-destructive-guardrails

Conversation

@mechkw

@mechkw mechkw commented Aug 26, 2026

Copy link
Copy Markdown

Summary

Align destructive-operation safety across the MCP server's two tool planes while preserving the autonomous, single-credential workflow:

  • gate gf_delete_field with the same GRAVITY_FORMS_ALLOW_DELETE policy already used for form, entry, and feed deletion;
  • gate destructive dynamic GravityKit abilities locally as well as through their WordPress permission_callback;
  • propagate WordPress Ability safety annotations to MCP ToolAnnotations, so clients can correctly distinguish read-only, mutating, destructive, and idempotent tools;
  • let dynamic destructive abilities inherit GRAVITY_FORMS_ALLOW_DELETE, with an optional GRAVITYKIT_ALLOW_DELETE override for deployments that need separate policy.

Why

The previous behavior had two inconsistent paths:

  1. gf_delete_field replaced the complete form without consulting api.allowDelete, even though every other static delete operation required the flag.
  2. Dynamic tools discarded the Ability API's annotations and relied only on server-side permissions. MCP clients therefore could not classify those tools, and the local delete policy did not cover destructive dynamic abilities.

For autonomous operators, the clean model is one explicit full-authority switch plus rollback-first execution—not per-object credential swapping or misleadingly incomplete guardrails. For conservative deployments, deletes remain off by default and the product-specific override can be set independently.

Compatibility

  • No tool names, input schemas, routes, credential formats, or non-destructive behavior change.
  • Existing deployments with GRAVITY_FORMS_ALLOW_DELETE=true retain delete access across both tool planes.
  • GRAVITYKIT_ALLOW_DELETE is optional; when omitted it inherits the existing Gravity Forms flag.
  • Destructive dynamic abilities are now blocked when both flags are false. This is the intended security hardening for the previously uncovered path.
  • WordPress capability checks and Foundation per-ability enable/disable controls remain in force.

Verification

  • npm run prepublishOnly — passed
    • unit suites
    • Node test suite
    • field validation
    • GravityView tests
    • package lint
    • documentation freshness
  • Added regression coverage for:
    • blocked field deletion performing no write;
    • field-delete tool description advertising the gate;
    • dynamic Ability annotation normalization;
    • environment-policy inheritance/override;
    • blocked destructive abilities never reaching WordPress;
    • explicitly allowed destructive abilities retaining the correct HTTP method.
  • Packaged the branch and completed a real stdio MCP handshake through Hermes against a local WordPress/Gravity Forms mock: 27 tools discovered.

Operational note

Snapshots, readback diffs, browser QC, and automatic rollback are orchestration concerns because the MCP server cannot infer the user's requested task boundary or guarantee restoration of every WordPress-side effect. This patch deliberately keeps that policy out of core while ensuring the server's local guardrails and MCP metadata are truthful and consistent.

cc @zackkatz — this is meant as a small, compatibility-oriented hardening patch rather than a broader privilege-model redesign. I would appreciate your view on whether the shared-flag inheritance is the right default for upstream.

Summary by CodeRabbit

  • New Features

    • Added configurable protection for destructive operations, including field deletion.
    • Added support for overriding deletion access through GRAVITYKIT_ALLOW_DELETE, with fallback to the existing Gravity Forms setting.
    • Improved tool metadata and documentation for deletion requirements.
  • Bug Fixes

    • Prevented delete actions from executing or persisting changes when deletion is disabled.
    • Normalized tool annotations for consistent behavior.

@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

  • Run on-demand review

On-demand reviews are free for the next 26 days. After that, they cost $0.25 per reviewed file.

Or wait 52 minutes for your next included review.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3a4609d2-9ab5-4403-be73-37b88a71ca60

📥 Commits

Reviewing files that changed from the base of the PR and between 3570d10 and a378208.

📒 Files selected for processing (2)
  • src/abilities/loader.js
  • test/abilities-loader.test.js

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 23dd3080-b0fb-4c7e-b0c0-d7f6e9a9046b

📥 Commits

Reviewing files that changed from the base of the PR and between 8ecd056 and 3570d10.

📒 Files selected for processing (7)
  • .env.example
  • README.md
  • src/abilities/loader.js
  • src/field-operations/field-manager.js
  • src/field-operations/index.js
  • test/abilities-loader.test.js
  • test/field-manager.test.js

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


Walkthrough

The change adds GravityKit deletion configuration, normalizes MCP annotations, and gates destructive ability and field deletion execution. Documentation and tests cover configuration inheritance, blocked operations, permitted DELETE requests, and tool metadata.

Changes

Deletion Policy and Ability Execution

Layer / File(s) Summary
Deletion policy and annotation contract
.env.example, README.md, src/abilities/loader.js, test/abilities-loader.test.js
The loader resolves GRAVITYKIT_ALLOW_DELETE with fallback to GRAVITY_FORMS_ALLOW_DELETE. WordPress annotations are normalized into MCP hints. Tests cover policy precedence and annotation conversion.
Destructive ability execution gate
src/abilities/loader.js, test/abilities-loader.test.js
Catalog-generated tools receive allowDelete. Destructive handlers reject execution when deletion is disabled and use DELETE requests when enabled.
Field deletion enforcement and documentation
src/field-operations/field-manager.js, src/field-operations/index.js, README.md, test/field-manager.test.js
deleteField rejects disabled deletion before persistence. Tool descriptions and tests document and verify the ALLOW_DELETE=true requirement.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 3570d

This change aligns destructive-operation gating and tool metadata across the two tool planes while preserving existing non-destructive behavior. The documented checks passed, and no actionable merge-blocking risk remains beyond normal review.

Sequence Diagram(s)

sequenceDiagram
  participant MCPClient
  participant loadAbilitiesAsTools
  participant buildTools
  participant WordPressAPI
  MCPClient->>loadAbilitiesAsTools: load catalog tools with allowDelete
  loadAbilitiesAsTools->>buildTools: pass deletion policy
  buildTools->>buildTools: normalize annotations and create handler
  buildTools->>WordPressAPI: dispatch DELETE when deletion is enabled
``

<!-- walkthrough_end -->
<!-- pre_merge_checks_walkthrough_start -->

<details>
<summary>🚥 Pre-merge checks | ✅ 5</summary>

<details>
<summary>✅ Passed checks (5 passed)</summary>

|         Check name         | Status   | Explanation                                                                                                                                                                                               |
| :------------------------: | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|     Docstring Coverage     | ✅ Passed | Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 5 files. (2 skipped: 2 … |
|     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 accurately and concisely summarizes the main change: aligning destructive-operation gates across the static and dynamic tool planes.                                                            |

</details>

<details>
<summary>Full details: Docstring Coverage</summary>

**Explanation**

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 5 files. (2 skipped: 2 unsupported.)

</details>

</details>

<!-- pre_merge_checks_walkthrough_end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

<details>
<summary>🧪 Generate unit tests (beta)</summary>

- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Create PR with unit tests

</details>

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=GravityKit/MCP&utm_content=13)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>


<sub>Comment `@coderabbitai help` to get the list of available commands.</sub>

<!-- tips_end -->
Loading

@mechkw

mechkw commented Aug 26, 2026

Copy link
Copy Markdown
Author

Follow-up hardening in a378208: an independent review caught that WordPress Abilities uses null for unknown annotations. The mapper now omits unknown/null MCP hints instead of converting them to false, preserving MCP's conservative defaults (notably destructiveHint defaults to true when unspecified). Added a regression test for the null/unknown case; targeted ability-loader and field-manager suites pass.

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