feat: GravityKit abilities plane (dynamic product tools via Foundation) + credential-aware auth#5
Conversation
Adds 30 gv_* tools alongside the existing gf_* surface, covering full
View authoring against the GravityView Inspector REST API.
Authoring flow: gv_create_view (defaults to gravityview-layout-builder,
accepts per-zone template_ids for Multiple/Single Entry divergence) →
gv_create_grid_row (surface=fields|widgets) → gv_apply_view_config for
bulk one-shot writes OR surgical tools (gv_add_view_field /
gv_patch_view_field / gv_move_view_field) for incremental edits. For
search bars: gv_add_search_field / gv_patch_search_field /
gv_remove_search_field write the modern search_fields_section tree;
existing legacy widgets auto-migrate on first save through this API.
Discovery: gv_list_templates / gv_list_widgets / gv_list_grid_row_types
/ gv_list_widget_zones / gv_list_search_zones / gv_list_view_forms /
gv_list_available_fields / gv_get_view_areas / gv_get_view_field_schemas.
gv_get_field_type_schema dispatches by kind (field | widget |
search_field) so one tool covers every type catalogue.
Move semantics borrowed from block-mcp: to.before_slot / to.after_slot
for ref-relative placement, position="start"|"end"|integer for symbolic.
Concurrency via ifMatch="auto" pulls from a per-view version cache.
Compact responses by default (compact=false for raw).
Validator pre-flights structural mistakes (mode enum, required fields,
LB area row_uid existence) and optionally schema-aware setting validation
when validateAgainstSchemas: true is passed.
Files:
- src/gravityview-client.js: HTTP client (Basic auth via WP App
Password, ETag/If-Match cache, all endpoint methods).
- src/view-operations/{index,view-validator}.js: tool definitions +
handler routing + client-side validation.
- src/index.js: server bootstrap, gv_* dispatch via wrapViewHandler
that surfaces the inspector REST envelope (gv_rest_* error codes)
rather than generic axios errors.
- src/tests/views.test.js: 25 tests covering construction, auth
fallback, discovery, create + version cache, apply with If-Match,
Layout Builder area-key encoding, allow-delete guard, render
GET-vs-POST, and validator (structural + schema-aware).
- package.json: test:views script wired into test:all.
Replaces the hand-maintained viewToolDefinitions array with a dynamic
loader: on MCP startup, fetch /wp-json/wp-abilities/v1/abilities, filter
to the gk-gravityview/* namespace, and synthesise a {name, description,
inputSchema} tuple per ability. HTTP method is derived from annotations
(readonly→GET, destructive→DELETE, otherwise POST). Tool naming is the
existing convention: gk-gravityview/list-layouts → gv_list_layouts.
The hand-maintained tool defs stay as a fallback for older WP installs
without the abilities-api package or when the catalog is unreachable.
GET/DELETE inputs ship as bracketed query params (input[key][nested]=v)
so WordPress REST rebuilds the nested object straight off the URL —
the abilities-api controller otherwise hands ?input=… straight to the
schema validator and complains "input is not of type object".
Drops the GRAVITYVIEW_ALLOW_DELETE env gate now that no "delete the
whole View" ability exists; field/widget/row removal is normal authoring
(reversible by re-adding) and status: trash is the only soft-remove
path, gated server-side by the WP delete_post capability. The
GRAVITY_FORMS_ALLOW_DELETE gate stays for entry/form/feed deletes —
those genuinely destroy data.
Adds src/tests/views-stress.test.js — 114 live tests against dev.test
covering the full catalog: hostile payloads, optimistic-concurrency
edge cases, schema-driven validation per setting type, search-field
domain delegation, area-key prefix/length/control-char validation,
duplicate-view + set-view-status round-trips, and an anti-test
proving no permanent-delete ability is registered.
Adds demo-abilities.mjs — a cold-start walk through the abilities
surface for onboarding + manual smoke tests.
Adds 9 new live stress tests covering the new abilities + behaviour
shipped in the GravityView feature/3.0-config-editor branch:
* gv_list_views — substring search lands a freshly-minted View;
form_id filter narrows correctly; pagination metadata round-trips.
* gv_get_view_config `include` projection — narrows the response to
just the requested top-level keys; view_id always present.
* Dry-run on gv_apply_view_config — meta unchanged after dry; flag +
would_apply stamped on the response.
* Dry-run on gv_patch_view_field — second write at dry_run=true does
not overwrite the value persisted by the prior real write.
* Dry-run on gv_add_view_field — slot count unchanged after dry-run.
* Catalog: every gk-gravityview ability advertises a non-empty
next_steps array, each entry is { ability:gk-gravityview/*, when:* }.
* Discovery bridge: list-layouts has_grid description names
list-grid-row-types AND list-view-areas as the next steps.
* Field presets: default catalog is empty (filter-driven, core ships
none).
* Field presets: apply-field-preset returns 404 for an unknown
preset id.
123/123 passing.
…ess tests Loader change: * abilities-loader.js now accepts an array of namespace prefixes (default ['gk-gravityview', 'gk-multiple-forms']) instead of a single string. Both namespaces collapse into the `gv_*` MCP tool prefix because they're conceptually one product family from the agent's point of view. Backward compatible — string args still work via the array-or-string normalisation at the top of loadAbilitiesAsTools. Tests: Basic coverage (12 tests): * catalog exposes gv_list_joins / gv_apply_joins / gv_list_joinable_fields * list-joins on a no-joins View returns empty + count=0 * list-joinable-fields enumerates form fields + entry-property aliases * apply-joins dry_run flags response + does NOT persist * apply-joins persists + list-joins inflates form/field labels * apply-joins is replace-not-merge (3 → 1 → 0 cycle) * apply-joins rejects malformed rows with HTTP 400 (no partial write) * apply-view-config writes joins via the cross-plugin filter * get-view-config include=[joins] projection narrows shape * list-views match_joined surfaces Views joining a form * list-available-fields includes joined_form_fields tagged with form_id * every gk-multiple-forms/* ability advertises a next_steps annotation Deep authoring coverage — exercises mixed-form field placement in real View areas (5 tests): * field slots from primary AND joined forms coexist in one area (with field-id collision: both forms have id=1) * 3-form join + fields from each form land in distinct areas * apply-view-config bulk: joins + fields from both forms in one call * dry_run on mixed-form bulk apply does NOT persist any slot * apply-joins clears + replaces, list-joins reflects each step Total: 140 passed, 0 failed (was 123 baseline → +17 MFV tests). All run live against dev.test admin/admin.
The startup-time blocking load was sticky on failure: once the cert / network / WP-not-yet-booted path failed, abilityToolDefinitions stayed null for the lifetime of the Node process and only Claude Code restart (not /mcp reconnect, which doesn't re-fork) recovered. This rewires the loader to: 1. Fire-and-forget eager kickoff in initializeClient() — no startup latency, no blocking the MCP handshake on a slow / down WP. 2. Single-flight ensureAbilitiesLoaded() promise. Concurrent callers share it; on rejection the cache clears so the NEXT call retries. 3. ListTools awaits up to 2s — covers a warm cold-start fetch on dev.test (~800ms) without ever feeling like a hang. 4. Every gv_* tool call awaits with no timeout (caller is willing). Cache hit on the success path = zero overhead. Failures self-heal on next call (sleep/wake, valet still booting, cert mid-fix all recover without an MCP restart). 5. tools/list_changed notification on successful load — clients refetch the catalog so abilities-derived schemas (joins on apply-view-config, gv_apply_joins, gv_list_joins) replace the legacy ones in their cache. 6. New gv_reload_abilities tool — manual escape hatch when you fixed a WP issue and want the refresh now without firing another gv_*. Verified: 140/140 views-stress tests pass against dev.test.
Tools 29–36 (auto-generated from the abilities catalog) emitted
`inputSchema` as a raw parameter array; another tool emitted
`properties` as an array (PHP-empty-assoc → JSON `[]`). Both forms
fail Zod validation during MCP `tools/list`, which broke every
gv_* tool on reconnect.
`normalizeInputSchema()` in abilities-loader coerces both shapes
into `{ type: "object", properties: <Record<string,JSONSchema>>, … }`,
deriving keys from each descriptor's name/slug/key when wrapping an
array, lifting `required: true` to the outer `required` array, and
preserving any sibling JSON Schema keys (additionalProperties,
description, etc.).
Adds src/tests/abilities-loader.test.js (16 tests) including the
MCP-contract assertion every generated tool's inputSchema must
satisfy: object root, type === 'object', properties is a non-array
object. Reproduces both wire-format failures via a synthetic
catalog so we never regress on the empty-properties or
array-input-schema shapes again.
Source chain: Foundation catalog (/wp-json/gravitykit/v1/abilities) first — server-side GK filtering, disabled omitted, paginated — with WP core (/wp-abilities/v1/abilities) as fallback for connections that can't pass the catalog's manage_options gate, filtered on meta.gk_registered_by. The server now owns tool naming exclusively (mcp_tool_name from Foundation's MCP_TOOL_PREFIXES). Removed the client-side gv_ name derivation and the hardcoded namespace allow-list — both were compat with code that never shipped (main/npm 2.1.1 are GF-only, no abilities). Abilities arriving without mcp_tool_name are skipped with a warning. Removed the never-released hand-maintained gv_* tool definitions and handlers (src/view-operations/index.js) and all fallback wiring; the abilities catalogs are the only source of gv_* tools. When unreachable, gv_* tools are absent and the per-call self-heal / gv_reload_abilities retries. Added a tool-name collision guard (first claimant wins, later ones logged and skipped) and updated the server instructions string to the ability-derived tool names (gv_view_create, gv_layouts_list, …). Requires server-side: Foundation to stamp mcp_tool_name into ability meta (for the WP-core fallback path) and/or relax the gravitykit/v1 catalog permission so non-admin authors can list abilities.
src/abilities/loader.js (was src/view-operations/abilities-loader.js)
The dynamic tool pipeline is product-agnostic — it serves every
GravityKit product from the Foundation catalog, so it no longer
lives under a GravityView-named folder.
src/wp-client.js (new) WordPressClient
Extracted the authenticated WP-root transport (base URL, app-password
auth, TLS, timeout) that the abilities loader rides. Runtime no
longer constructs anything GravityView-specific.
src/gravityview/ (product-specific test harness)
inspector-client.js GravityViewInspectorClient extends
WordPressClient, mounts /gravityview/v1.
Test/demo harness only — the Inspector routes
are registered server-side solely under
DOING_GRAVITYVIEW_TESTS. Also fixed three
stale ability slugs (available-fields-get,
field-type-schema-get, search-input-types-list).
view-validator.js moved alongside its product harness.
Env vars renamed (never released): GRAVITYVIEW_BASE_URL → GRAVITYKIT_WP_URL,
GRAVITYVIEW_WP_USERNAME/_APP_PASSWORD → GRAVITYKIT_WP_USERNAME/_APP_PASSWORD,
GRAVITYVIEW_TIMEOUT → GRAVITYKIT_TIMEOUT. GF fallbacks unchanged.
Documented in .env.example.
Tests: 264 unit + 21 loader, all passing.
…ation Plane A (Gravity Forms): the 26 static gf_* tools over GF REST v2 — works on any Gravity Forms site, no Foundation/WP 6.9 required. Tool literals extracted to GF_TOOL_DEFINITIONS at module scope. Plane B (GravityKit abilities): dynamic tools from the Foundation catalog (all GK products) with WP-core fallback. Lights up only when Foundation is active on the connected site. Changes: - initializeClient() split into per-plane initializers. A GravityView site without GF REST keys now still gets abilities tools; a GF-only site is unaffected. Throws only when NEITHER plane has credentials. Per-plane 60s retry cooldown prevents re-validation storms. - Abilities load failures now back off for 60s instead of re-fetching on every tools/list (Foundation-less sites no longer pay two failed requests per list, forever). gv_reload_abilities bypasses cooldown. - RESERVED_TOOL_NAMES: the loader's collision guard is seeded with all built-in tool names, so a future catalog-served gk-gravity-forms ability can never shadow the released gf_* contract. - Server instructions now state plane availability (gf_* works anywhere; product tools require Foundation on the site).
Four interlocking changes that get the live stress suite green:
1. methodForAbility (loader.js): require BOTH `destructive` AND
`idempotent` for DELETE. Foundation's run controller (in WordPress
core's wp-rest-abilities-v1-run-controller) only accepts DELETE
when both flags are set, matching WP-REST conventions. view-delete
is destructive but soft-trashes by default (force=false), so it is
not idempotent — sending DELETE got a 405. Now POSTs.
2. abilities-loader.test.js: extend the methodForAbility unit test to
cover the new logic, with an explicit regression case for
`destructive + !idempotent → POST`.
3. views-stress.test.js: re-apply the verb-first → noun-first rename
across 243 call sites (gv_apply_view_config → gv_view_config_apply
etc.). Foundation's `/wp-json/gravitykit/v1/abilities` endpoint
emits the canonical `mcp_tool_name` in noun-first shape, and the
loader (post-7474ab0) treats that as authoritative.
4. views-stress.test.js: correct three stale assertions that no longer
match Foundation's current contract:
- "NO permanent-delete ability exists" → reframed as "default
invocation soft-trashes" (mode=trash, deleted=true, force=false).
view-delete IS shipped, with a safe-by-default soft-delete path.
- dry-run response no longer flagged `would_apply` — Foundation
FIX-22/66 (commit 05053d3d on the Foundation feature branch)
intentionally dropped it as redundant with `dry_run`.
- discovery-bridge test now looks up `gk-gravityview/layouts-list`
(was `list-layouts`) and expects `grid-row-types-list` /
`view-areas-get` in the has_grid description (was the old
`list-grid-row-types` / `list-view-areas`).
- scripts/stress-abilities.mjs: synthetic 1,205-item paginated catalog exercising the products-filter naming model (declared gv_* prefixes + full-product-slug fallback names), collision/reserved-name guards, disabled/unnamed/foreign filtering, all schema normalization shapes, GET/POST/DELETE execution wire formats, the stamped meta.mcp_tool_name WP-core fallback path, and the empty-catalog self-heal throw. 19 checks; ~1ms per 1,205-item load, >1M handler calls/s. - loader.js: update comments — tool prefixes now come from each product's required mcp_prefix on gk/foundation/abilities/products (full-slug fallback), and mcp_tool_name is stamped into ability meta on both catalogs. No code changes needed: the loader was already shape-compatible.
Cross-checked against gravityforms/gravityforms#3716 (25 abilities + bundled MCP Adapter, registered on wp_abilities_api_init with show_in_rest:true under the gravityforms/* namespace). - Coexistence test: GF's abilities DO appear in the WP core catalog our fallback path reads; pin that the gk_registered_by metadata filter excludes them (core gravityforms/* and the two-slash add-on convention gravityforms/{addon}/{action}). - Layout grid normalization in FieldManager (ported from GF_Abilities_Handler_Forms::normalize_layout_group_ids): friendly layoutGroupId names hash to the editor's 8-char hex format — stable per form so sequential gf_add_field calls can share a row — and layoutGridColumnSpan clamps to the 1-12 grid. - Doc fix: loader header now states the destructive+idempotent DELETE rule (the methodForAbility code/tests were already fixed). Note: field-manager.test.js has 11 pre-existing failures under direct node --test (stale mock response shapes); not run by the custom runner.
createMockApiClient predated two client changes: getForm() resolving
{ form } and the 1.4.1 replaceForm() direct-PUT path. All 11 failures
were mock drift, not product bugs — field-manager.test.js now 31/31.
New test:field-ops script runs the four node:test-based files
(field-manager, field-registry, field-dependencies, field-positioner),
which were invisible to both the custom runner and test:all. Chained
into test:all so they can't silently rot again.
…y-feeds normalization
Found by running the live integration suite against a minted GF 2.10.3
site (Siteminter, http://localhost — the OAuth fallback path):
- OAuth signatures stringified arrays ({ include: [3] } signed as
include=3) while axios sent include[]=3 on the wire — every OAuth GET
with array params failed with 'invalid signature'. Affects released
2.1.1 on any non-HTTPS connection. Fix: shared flattenParams() turns
params into PHP bracket-index pairs (include[0]=3, paging[page_size]=2)
used by BOTH the signature base and a matching axios paramsSerializer,
with strict RFC 3986 encoding (rawurlencode parity: !'()* escaped) per
RFC 5849 §3.4.1.3.2 (encode, then sort by encoded name/value).
- listFeeds: GF returns a serialized WP_Error ({errors:{not_found:[...]}})
with HTTP 200 when a site has no feeds — normalize to feeds: [] so
callers always get an array.
Verified: full live integration suite 24/24 against GF 2.10.3 over
OAuth; unit 266, auth 22, feeds 25, field-ops 131 — all green.
…Auth Basic auth no longer hard-requires HTTPS client-side. Selection now matches what the Gravity Forms server actually accepts: - ck_/cs_ key pairs on plain HTTP sign with OAuth 1.0a automatically — GF only checks key-pair Basic auth over SSL (class-gf-rest-authentication.php: if ( is_ssl() )), so Basic with keys on http authenticates as nobody. - WordPress app-password credentials (username + application password) use Basic on local URLs (localhost, *.localhost, 127.x, ::1, *.test, *.local) with no opt-in — WP core authenticates them and GF's capability checks take over. No OAuth involved. - Explicit GRAVITY_FORMS_AUTH_METHOD always wins; remote-HTTP Basic needs GRAVITY_FORMS_ALLOW_HTTP_BASIC_AUTH=true and logs a warning. Integration harness: honor GRAVITY_FORMS_TEST_AUTH_METHOD and stop forcing an explicit 'basic' default that defeated auto-selection. New security coverage (skip cleanly without fixtures): unauthenticated requests denied; authenticated user without GF capabilities denied; read-only API key can read but not write. Live verification on minted GF 2.10.3 (Siteminter, http://localhost): 27/27 in all four configurations — auto→OAuth (keys), auto→Basic (app password), explicit oauth, explicit basic. Mocked suites: unit 269, auth 25, all green.
App passwords are now the recommended first-run credential (README, .env.example, AGENTS.md): one credential powers gf_* and, with Foundation, the GravityKit product tools; access follows WP capabilities. GF API keys move to the scoped-access (e.g. read-only) path. Documented the one unavoidable GF requirement — 'Enable access to the API' gates route registration for every credential type. Removed the active GRAVITY_FORMS_AUTH_METHOD=basic line from .env.example: with explicit method now honored everywhere, shipping it as a default forces Basic on remote HTTP. Replaced the stale 'silently falls back to OAuth' gotcha with the credential-aware rules.
Brings in cfd5261 (AI-facing input hints for server instructions and tool descriptions). Conflict resolution — src/index.js: - Server `instructions`: kept the feature-branch version. It already documents the same GF input behavior main added ("checkbox/multiselect arrays auto-normalized; multiselect values with commas get split by GF REST API") plus the full GravityView/abilities tool guidance. - gf_create_entry / gf_update_entry: re-applied main's improved descriptions into the refactored GF_TOOL_DEFINITIONS array (feature extracted the inline tool list into that constant). src/field-operations/index.js merged cleanly (main's gf_list_field_types summary-mode entry_input hints preserved). GF tool set verified identical (22 tools) before/after; no tools dropped.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds GravityView Abilities API integration: a new ChangesGravityView Abilities Integration & Auth Overhaul
Sequence Diagram(s)sequenceDiagram
participant MCPClient
participant MCPServer as src/index.js
participant AuthManager
participant WordPressPlane as WordPressClient
participant ensureAbilitiesLoaded
participant AbilitiesLoader as src/abilities/loader.js
participant WP_Abilities_API
MCPClient->>MCPServer: tools/list
MCPServer->>AuthManager: initializeGravityFormsPlane (lazy)
MCPServer->>WordPressPlane: initializeWordPressPlane (lazy)
MCPServer->>ensureAbilitiesLoaded: start with 2s timeout
ensureAbilitiesLoaded->>AbilitiesLoader: loadAbilitiesAsTools(wpClient, {reservedNames})
AbilitiesLoader->>WP_Abilities_API: GET /gravitykit/v1/abilities (Foundation, paginated)
alt Foundation reachable
WP_Abilities_API-->>AbilitiesLoader: catalog pages
else Foundation 404/empty
AbilitiesLoader->>WP_Abilities_API: GET /wp-abilities/v1/abilities (WP-core)
WP_Abilities_API-->>AbilitiesLoader: catalog filtered by gk_registered_by
end
AbilitiesLoader->>AbilitiesLoader: normalizeInputSchema + collision guard + build handlers
AbilitiesLoader-->>ensureAbilitiesLoaded: {definitions, handlers}
ensureAbilitiesLoaded-->>MCPServer: cached tools
MCPServer-->>MCPClient: GF tools + field-ops + gv_* tools + gv_reload_abilities
MCPClient->>MCPServer: call gv_view_create {title, form_id}
MCPServer->>ensureAbilitiesLoaded: self-heal check
MCPServer->>AbilitiesLoader: abilityToolHandlers.get("gv_view_create")
AbilitiesLoader->>WP_Abilities_API: POST /wp-abilities/v1/abilities/gv_view_create/run {input}
WP_Abilities_API-->>AbilitiesLoader: {data, etag}
AbilitiesLoader-->>MCPServer: result via wrapViewHandler
MCPServer-->>MCPClient: tool result
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes ✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
AGENTS.md (1)
127-127:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUpdate tool count from 28 to 26.
Line 127 still mentions "All 28 tool descriptions" but the tool count is now 26 (after removing
gf_list_form_feeds). This should read "All 26 tool descriptions" for consistency with the rest of the document and the actual tool set.Proposed fix
-- **Concise tool descriptions**: All 28 tool descriptions and property descriptions are terse to reduce tool-list overhead +- **Concise tool descriptions**: All 26 tool descriptions and property descriptions are terse to reduce tool-list overhead🤖 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 `@AGENTS.md` at line 127, Update the tool count reference in the line containing "All 28 tool descriptions and property descriptions are terse" by changing "28" to "26" to reflect the current number of tools after the removal of gf_list_form_feeds, ensuring the documentation accurately represents the actual tool set.
🧹 Nitpick comments (2)
.env.example (1)
57-58: 💤 Low valueReorder environment variables for consistency.
Line 58 (
GRAVITY_FORMS_MAX_RETRIES) should appear before line 57 (GRAVITY_FORMS_TIMEOUT) to maintain alphabetical/logical ordering, as flagged by dotenv-linter.Proposed reordering
-GRAVITY_FORMS_TIMEOUT=30000 -GRAVITY_FORMS_MAX_RETRIES=3 +GRAVITY_FORMS_MAX_RETRIES=3 +GRAVITY_FORMS_TIMEOUT=30000🤖 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 @.env.example around lines 57 - 58, The environment variables in .env.example are not in alphabetical order. Swap the positions of GRAVITY_FORMS_MAX_RETRIES and GRAVITY_FORMS_TIMEOUT so that GRAVITY_FORMS_MAX_RETRIES (line 57) appears before GRAVITY_FORMS_TIMEOUT (line 58), maintaining alphabetical consistency as required by dotenv-linter.Source: Linters/SAST tools
src/gravity-forms-client.js (1)
28-29: 💤 Low valueUser-Agent version mismatch.
GravityFormsClientuses version2.1.0whileWordPressClientuses2.1.1. These should be consistent, ideally derived from a single source.🤖 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/gravity-forms-client.js` around lines 28 - 29, The User-Agent header in GravityFormsClient is set to version 2.1.0 but WordPressClient uses 2.1.1, creating an inconsistency. Extract the version number into a shared constant (e.g., a VERSION constant or from package.json) and update both GravityFormsClient and WordPressClient to reference this single source of truth, ensuring they both report the same version in their User-Agent headers.
🤖 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 `@demo-abilities.mjs`:
- Around line 9-15: Replace the absolute file paths in the import statements for
WordPressClient and the loader functions with relative imports that will work
from any machine, using relative path syntax like '../src/wp-client.js' and
'../src/abilities/loader.js' based on the script's location. Additionally,
update the documentation comment at the beginning of the file to correct the
filename reference from '/tmp/abilities-demo.mjs' to 'demo-abilities.mjs' and
clarify the correct command to run the script from the project root with the
correct relative path or how to invoke it properly so other developers
understand how to use this demo script.
In `@src/field-operations/field-manager.js`:
- Around line 227-233: In the layoutGridColumnSpan validation block, replace the
parseInt(field.layoutGridColumnSpan, 10) call with
Number(field.layoutGridColumnSpan) to strictly validate numeric values, and
change the Number.isFinite(span) check to Number.isInteger(span) to reject
floats and partial numeric strings like "6wide". This ensures only proper
integers are accepted and non-numeric values (including mixed strings) are
dropped via the delete statement. Note that Number("") returns 0 which will be
kept rather than dropped, but if strict rejection of empty strings is required,
you can add an additional check for non-empty strings before the Number
conversion.
In `@src/gravityview/inspector-client.js`:
- Around line 491-498: Fix indentation issues in
src/gravityview/inspector-client.js at two locations. At lines 491-498 in the
removeSearchField method, indent the lines containing const config, config.data,
const response, and the httpClient.delete call to properly align with the method
body indentation level. Apply the same indentation fix to lines 537-543 in the
removeViewWidget method, ensuring const config, config.data, const response, and
the httpClient.delete call are consistently indented to match the surrounding
method body structure.
- Around line 402-411: Fix the indentation inconsistency in the deleteGridRow
method. Review the entire method body starting from the comment about
axios.delete and ensure all lines (the config declaration, the if statement for
surface, the httpClient.delete call with its parameters, the cacheVersion call,
and the return statement) use consistent indentation that matches the
indentation style used in the rest of the class.
- Around line 345-351: The `removeViewField` method has inconsistent indentation
where the `const response` declaration line is missing leading indentation,
breaking the established code style. Add proper indentation (typically 4 spaces
or one tab level) to the `const response` line so it aligns consistently with
the other statements in the method body like `this.cacheVersion` and `return
response.data`.
In `@src/gravityview/view-validator.js`:
- Around line 104-106: The field_id validation in the error-checking condition
at line 104 is insufficient and allows invalid values like false, objects,
arrays, and whitespace-only strings to pass validation. Enhance the validation
check for field_id to ensure it is a non-empty string by verifying that it
exists, is of string type, and is not empty or whitespace-only. Replace the
current condition that only checks for the key existence and null/empty string
with a stricter validation that rejects falsy values, non-string types, and
whitespace-only strings before throwing the validation error.
---
Outside diff comments:
In `@AGENTS.md`:
- Line 127: Update the tool count reference in the line containing "All 28 tool
descriptions and property descriptions are terse" by changing "28" to "26" to
reflect the current number of tools after the removal of gf_list_form_feeds,
ensuring the documentation accurately represents the actual tool set.
---
Nitpick comments:
In @.env.example:
- Around line 57-58: The environment variables in .env.example are not in
alphabetical order. Swap the positions of GRAVITY_FORMS_MAX_RETRIES and
GRAVITY_FORMS_TIMEOUT so that GRAVITY_FORMS_MAX_RETRIES (line 57) appears before
GRAVITY_FORMS_TIMEOUT (line 58), maintaining alphabetical consistency as
required by dotenv-linter.
In `@src/gravity-forms-client.js`:
- Around line 28-29: The User-Agent header in GravityFormsClient is set to
version 2.1.0 but WordPressClient uses 2.1.1, creating an inconsistency. Extract
the version number into a shared constant (e.g., a VERSION constant or from
package.json) and update both GravityFormsClient and WordPressClient to
reference this single source of truth, ensuring they both report the same
version in their User-Agent headers.
🪄 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: f09fcdf6-d652-49b3-b517-3338f271fd99
📒 Files selected for processing (24)
.env.example.mcp.jsonAGENTS.mdCLAUDE.mdREADME.mddemo-abilities.mjsmcp.jsonpackage.jsonscripts/stress-abilities.mjssrc/abilities/loader.jssrc/config/auth.jssrc/field-operations/field-manager.jssrc/gravity-forms-client.jssrc/gravityview/inspector-client.jssrc/gravityview/view-validator.jssrc/index.jssrc/tests/abilities-loader.test.jssrc/tests/authentication.test.jssrc/tests/field-manager.test.jssrc/tests/integration.test.jssrc/tests/run.jssrc/tests/views-stress.test.jssrc/tests/views.test.jssrc/wp-client.js
The constructor throws 'WordPress client requires credentials…' but the test asserted the substring 'WordPress credentials', which never appears. Align with the actual message (and with the sibling base-URL test, which asserts a literal substring of its error).
Gravity Forms returns a serialized WP_Error with HTTP 200 when feeds can't
be enumerated. listFeeds already normalized the not_found variant to []; a
fresh GF install with no feed-based add-on instead returns missing_table
(the wp_gf_addon_feed table only exists once a GFFeedAddOn's upgrade_base()
has run). Generalize the normalization to any HTTP-200 WP_Error so
gf_list_feeds always returns an array on any site.
Integration test: the "Create test feed" pre-check listed MailChimp feeds
to detect availability, but with the normalization above that now returns
{feeds: []} on a fresh site and the check falsely concluded MailChimp was
present, then hard-failed on create. Replace the brittle pre-check with an
attempt-and-skip: try the create, and skip (not fail) when the error shows
the add-on or its table isn't available. Verified live against a Siteminter
WP site across no-table, table-present, and add-on-inactive states (27/27).
The abilities catalog renamed tools from verb-noun to noun-verb (list-layouts -> layouts-list, create-view -> view-create, etc.); the demo still used the old ability and gv_* handler names and crashed at step 1c. Update all names to match the live catalog, replace hardcoded absolute import paths with relative ones, and make the View round-trip self-contained by minting a throwaway form (when GRAVITYKIT_DEMO_FORM_ID is unset) and cleaning up both the View and the form at the end. Verified end-to-end against a live GravityView 3.0.0 site.
…i skip] gf_list_form_feeds was removed (gf_list_feeds with form_id covers it); the Response Shapes section still listed it as a tool. Verified every gf_*/gv_* name in the server instructions, demo, README, AGENTS.md and mcp.json against the 75 registered tools (26 gf_* + 49 live gv_*) — all match.
The published package was shipping 25 test files and 5 dev scripts: the `files` allowlist listed `src/` and `scripts/` wholesale, and with a `files` field present npm ignores `.npmignore` entirely — so its test/dev excludes silently did nothing. Packaging (industry-standard allowlist, no .npmignore): - Relocate tests src/tests/ -> top-level test/ so the `src` allowlist entry no longer pulls them in. Rewrite their `../` imports to `../src/` and fix bug-fixes.test.js path anchors (srcDir/projectDir) for the new depth. - Tighten `files` to a precise allowlist (src, mcp.json, .env.example, README, LICENSE, CLAUDE.md, AGENTS.md) and delete the dead `.npmignore`. Tarball: 61 -> 31 files, 0 tests, 0 dev scripts. - Add publint (npm run lint:package) and a prepublishOnly gate that runs the offline suites + publint before publish (omits the live integration test so publishing never hits a real site). Dev tooling: - Add scripts/verify-tool-names.mjs: cross-checks every gf_/gv_ name in the server instructions, docs and demo against the tools the server actually registers (gv_* are generated from the live abilities catalog and can drift). Run via npm run verify:tool-names against a live site. - Document packaging and the verifier in AGENTS.md. Verified: full test:all incl. live integration green; npm pack --dry-run clean (31 files); publint passes; verify:tool-names passes.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
test/integration.test.js (2)
751-780: 💤 Low valueSilent
.catch(() => {})may mask unrelated initialization failures.Line 766 swallows all initialization errors. If the read-only key is invalid or there's a network issue, the test proceeds and
listFormsmay fail with a confusing error. Consider logging or at least checking the error type:- await roClient.initialize().catch(() => {}); + await roClient.initialize().catch((e) => { + // Read-only keys may fail certain init checks; continue to test actual permissions + console.log(` Note: initialize() threw: ${e.message}`); + });This preserves observability while allowing the test to continue.
🤖 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 `@test/integration.test.js` around lines 751 - 780, The `.catch(() => {})` on the roClient.initialize() call silently swallows all errors without any logging or inspection, which can mask unrelated initialization failures and cause confusing errors later when listForms is called. Replace the silent catch handler with code that logs the error and optionally checks the error type before deciding whether to suppress it. This will preserve observability of what actually happened during initialization while still allowing the test to continue to the read/write verification steps.
377-416: 💤 Low valueRegex pattern may not catch all MailChimp unavailability scenarios.
The regex at line 403 covers common cases but could miss variations like
"addon_slug gravityformsmailchimp is not registered"or"Feed add-on not active". Consider also matchingfeed.*notorgravityformsmailchimp.*notpatterns if the API returns those messages.That said, the current regex is reasonable for known error formats, and the fallback behavior (re-throw) ensures genuine failures aren't silently ignored.
🤖 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 `@test/integration.test.js` around lines 377 - 416, The regex pattern assigned to the unavailable constant needs to be enhanced to catch additional error message variations that may be returned by the MailChimp API. The current pattern covers common cases like "table does not exist" and "not installed", but should also match variations such as "feed" followed by "not" (for messages like "Feed add-on not active") and "gravityformsmailchimp" followed by "not" (for messages like "addon_slug gravityformsmailchimp is not registered"). Update the regex in the unavailable variable to include these additional patterns using alternation or broader matching to ensure the skip condition is triggered for all documented unavailability scenarios while still allowing genuine errors to be re-thrown.test/authentication.test.js (1)
371-377: ⚡ Quick win
isMaindetection may fail on Windows paths or certain Node invocations.The current logic
import.meta.url.endsWith(process.argv[1].replace(/.*\//, ""))strips only forward slashes. On Windows,process.argv[1]uses backslashes. A more robust approach:-const isMain = process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/.*\//, "")); +const isMain = process.argv[1] && import.meta.url === `file://${process.argv[1]}`;This matches the pattern used in
test/integration.test.jsat line 783.🤖 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 `@test/authentication.test.js` around lines 371 - 377, The isMain detection in test/authentication.test.js uses a regex pattern that only strips forward slashes, which fails on Windows paths that contain backslashes. Update the regex pattern in the isMain assignment (around line 371) to handle both forward and backward slashes, matching the robust approach already implemented in test/integration.test.js at line 783. Replace the current replace pattern to use a regex that matches either forward or backward slash separators, ensuring cross-platform compatibility.
🤖 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 `@scripts/verify-tool-names.mjs`:
- Around line 52-57: The ability-names discovery is fetching only a single page
from the `/wp-json/wp-abilities/v1/abilities` endpoint, causing valid
gk-gravityview/* names on subsequent pages to be missed during verification.
Implement pagination logic in the request call to fetch all available pages of
the abilities catalog and accumulate all matching ability names into the
abilityNames set, rather than building it from just the initial response. Check
the API response for pagination metadata (such as total pages or a next page
link) and continue fetching until all pages have been processed and added to the
abilityNames set.
---
Nitpick comments:
In `@test/authentication.test.js`:
- Around line 371-377: The isMain detection in test/authentication.test.js uses
a regex pattern that only strips forward slashes, which fails on Windows paths
that contain backslashes. Update the regex pattern in the isMain assignment
(around line 371) to handle both forward and backward slashes, matching the
robust approach already implemented in test/integration.test.js at line 783.
Replace the current replace pattern to use a regex that matches either forward
or backward slash separators, ensuring cross-platform compatibility.
In `@test/integration.test.js`:
- Around line 751-780: The `.catch(() => {})` on the roClient.initialize() call
silently swallows all errors without any logging or inspection, which can mask
unrelated initialization failures and cause confusing errors later when
listForms is called. Replace the silent catch handler with code that logs the
error and optionally checks the error type before deciding whether to suppress
it. This will preserve observability of what actually happened during
initialization while still allowing the test to continue to the read/write
verification steps.
- Around line 377-416: The regex pattern assigned to the unavailable constant
needs to be enhanced to catch additional error message variations that may be
returned by the MailChimp API. The current pattern covers common cases like
"table does not exist" and "not installed", but should also match variations
such as "feed" followed by "not" (for messages like "Feed add-on not active")
and "gravityformsmailchimp" followed by "not" (for messages like "addon_slug
gravityformsmailchimp is not registered"). Update the regex in the unavailable
variable to include these additional patterns using alternation or broader
matching to ensure the skip condition is triggered for all documented
unavailability scenarios while still allowing genuine errors to be re-thrown.
🪄 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: 384dc9cc-538d-4fce-879a-d21164b5b12c
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (29)
.npmignoreAGENTS.mdpackage.jsonscripts/verify-tool-names.mjstest/abilities-loader.test.jstest/authentication.test.jstest/bug-fixes.test.jstest/checkbox-expansion.test.jstest/compact.test.jstest/entries.test.jstest/feeds.test.jstest/field-dependencies.test.jstest/field-manager.test.jstest/field-operations-e2e.test.jstest/field-operations-integration.test.jstest/field-positioner.test.jstest/field-registry.test.jstest/field-validation.test.jstest/forms.test.jstest/helpers.jstest/integration.test.jstest/mutex.test.jstest/run.jstest/sanitize.test.jstest/server-tools.test.jstest/submissions.test.jstest/validation.test.jstest/views-stress.test.jstest/views.test.js
💤 Files with no reviewable changes (2)
- test/run.js
- .npmignore
✅ Files skipped from review due to trivial changes (6)
- test/mutex.test.js
- test/sanitize.test.js
- test/entries.test.js
- test/forms.test.js
- test/field-validation.test.js
- test/field-operations-integration.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
- package.json
…[ci skip] CLAUDE.md is now a one-line `@AGENTS.md` import. Moved the only unique CLAUDE.md content — the Project Identity block (package + version) — into AGENTS.md, retargeted the release checklist's version-bump step to AGENTS.md, added AGENTS.md to the repo map, and dropped CLAUDE.md from the verify-tool-names surfaces (it re-exports AGENTS.md, already checked). Everything else (commands, env, critical rules, release steps) was already covered in AGENTS.md.
The reload tool operates on the whole GravityKit abilities plane (the product-agnostic Foundation catalog), not GravityView specifically — `gv_` is GravityView's product prefix. Rename the built-in to `gk_` so the GravityKit-wide control tool is distinct from the `gv_*` product tools. Also surface it in the server instructions so an agent knows how to (re)load the catalog when gv_* tools are missing. Pre-release; no public API impact.
AGENTS.md is now the single source of truth, so accuracy matters. Recast it around two planes — Gravity Forms (primary, 26 gf_* tools) and GravityKit (secondary, dynamic gv_* from the Foundation Abilities catalog; GravityView the only product so far) — documenting the previously-undocumented abilities plane (WordPressClient, abilities loader, gk_reload_abilities, the test/demo-only gravityview/ harness). Removed all brittle file:line citations (cite symbols instead), corrected counts (45 field types; 26 GF tools), and fixed the stale test/ path. Add scripts/check-docs.mjs (npm run lint:docs): an offline guard that fails on doc drift — repo-map coverage (via `git ls-files --cached --others --exclude-standard`, so it respects .gitignore without a hand-rolled parser), tool/field-count mismatches, and any file:line citation. Wired into prepublishOnly alongside publint. Verified: lint:docs, lint:package, and the offline test suites all pass. Live verify:tool-names confirmed the gv_* names against the catalog earlier this session (re-run currently blocked by an unrelated GravityView Composer autoload fatal in the local plugin checkout).
Review follow-ups (verified live against the running site):
- verify-tool-names: the WP Abilities endpoint paginates (default per_page
50; the site has 51 abilities over 2 pages), so the single-page fetch
missed gk-gravityview/* names on page 2 — abilityNames was 48 vs the
loader's 49. Walk all pages (per_page=100 + X-WP-TotalPages). Now 49/49.
- authentication.test.js: isMain basename strip only handled "/", failing
direct `node` execution on Windows paths. Strip "/" or "\". (Same pattern
exists in ~10 sibling test files; fixed only the flagged one per minimal
scope.) Note: the suggested integration.test.js reference (file:// exact
match) isn't Windows-robust either, so used a separator-agnostic regex.
- integration.test.js: read-only-key test swallowed initialize() errors
silently; now logs them so a real init failure stays visible.
- integration.test.js: the create-feed "unavailable" skip regex required a
space after "add-?on", so "addon_slug ... is not registered" wasn't
caught; dropped the space. ("Feed add-on not active" was already covered
by the existing "not active" branch.)
Verified: verify:tool-names 49/49 all-match; test:auth 25; test:unit 269;
live integration 27; lint:docs green.
…S.md Redid the prior review fixes test-first (RED watched, then GREEN), pulling the logic out of inline test/script bodies into units with real coverage: - test/helpers.js: isMainModule (POSIX+Windows path basename), feedUnavailable (feed add-on/infra "unavailable" detection), settleWithReport (report, don't swallow, a rejected init). New test/helpers.test.js — 13 cases; each bug case was confirmed failing against the old behavior before the fix. - scripts/lib/ability-catalog.mjs: collectAbilityNames() walks every page of the paginated WP Abilities catalog. New test/ability-catalog.test.js — the multi-page case failed against the single-page stub, then passed. - Wired call sites to the helpers (authentication.test.js, integration.test.js, verify-tool-names.mjs), removing the inline copies. No STUB placeholders remain — each was replaced during GREEN. - package.json: test:lib runs the node:test units; added to test:all and prepublishOnly. - AGENTS.md: new "Test-Driven Development (required)" section mandating RED/GREEN/REFACTOR; Extension Patterns now says write the failing test first; repo map + Testing list updated. Verified: test:lib 16, test:unit 269, test:field-ops 131, live integration 27, verify:tool-names 49/49 all-match, lint:docs green, npm pack clean (31 files).
The two node:test suites had near-duplicate scripts (test:field-ops + test:lib). Merge into a single honestly-named test:node listing all six node:test files; update test:all, prepublishOnly, and the AGENTS.md Testing list / TDD section. No test logic changed (147 node:test cases still run).
Verified each finding against current code; fixed the still-valid ones. Behavioral (test-first, RED watched then GREEN): - field-manager: layoutGridColumnSpan now validated with Number()+ Number.isInteger() (was parseInt/isFinite), so "6.5"/"6wide"/floats/ empty/whitespace are dropped instead of coerced. (test/field-manager.test.js) - view-validator: field_id must be a finite number or non-empty string — rejects false/objects/arrays/whitespace that previously passed. Kept numeric support (it's coerced via String(item.field_id)); the reviewer's "require string type" would have broken numeric ids. (test/views.test.js) - User-Agent: single-sourced via new src/version.js (USER_AGENT from package.json). GravityFormsClient said 2.1.0, WordPressClient 2.1.1; now both match the package version. (test/user-agent.test.js) Non-behavioral (TDD-exempt): - inspector-client: fixed four flush-left lines (removeViewField, deleteGridRow comment, removeSearchField, removeViewWidget). - .env.example: alphabetized GRAVITY_FORMS_MAX_RETRIES before _TIMEOUT. Skipped — already addressed earlier: - demo-abilities.mjs absolute imports + /tmp header (fixed in ded240c). - AGENTS.md "28 tool descriptions" (the AGENTS.md rewrite dropped the count). Wiring: test:node now includes user-agent.test.js; version.js added to the AGENTS.md repo map. Verified: test:node 151, test:views 27, test:unit 269, live integration 27, verify:tool-names 49/49, lint:docs + publint green.
README's Features + Available Tools were Gravity-Forms-only. Add a Features bullet and a 'GravityKit Products (gv_*, dynamic)' section covering the runtime-generated GravityView tools and gk_reload_abilities. Verified all README tool names resolve against the live catalog (28 referenced, 0 unknown).
gv_* is GravityView's prefix specifically; the GravityKit plane is product-agnostic — each add-on registers tools under its own server-owned prefix (that's why the cross-product reload tool is gk_reload_abilities, not gv_). Reframe the plane as 'GravityKit (dynamic)' in README + AGENTS.md, with GravityView noted as the first product (prefix gv_*).
Replace the clone + .env + 'node /path/to/MCP/src/index.js' setup with the published-package flow: 'npx -y @gravitykit/mcp' in the MCP client's command, credentials in the client's env block. Drops the clone/npm-install steps from Quick Start (local-checkout dev still covered under Contributing); notes key- pair, self-signed, and version-pin options; Configuration + Troubleshooting now point at the env block (npx) or .env (clone).
…— TDD All four findings fixed test-first (RED watched, then GREEN). Logic for the three index.js issues was extracted into src/server-runtime.js so it's unit-testable (index.js self-runs main() and isn't importable). - #1 serial init: WordPress plane now starts before the Gravity Forms REST probe is awaited, so a slow/bad GF config no longer stalls the WP plane or the abilities load. (runPlaneInit) - #2 tool advertising: gf_* + field-op tools are listed only when the GF plane is live, so a WP-only install doesn't advertise tools that error on call. (buildToolList, gated on gravityFormsClient) - #3 dispatch: the call router no longer hard-codes name.startsWith('gv_'). It routes by ability-handler-map membership, so any GravityKit product prefix (gc_, …) dispatches, not just GravityView's gv_. (classifyAbilityCall) - #4 wp-client: WordPressClient refuses to send Basic auth over a remote plain-HTTP URL (credential exposure) unless GRAVITY_FORMS_ALLOW_HTTP_BASIC_AUTH =true, reusing isLocalUrl — matching the GF plane's guard. Tests: test/server-runtime.test.js (10), test/wp-client.test.js (4), wired into test:node. server-runtime.js added to the AGENTS.md repo map. Verified: test:node 165, test:unit 269, prepublishOnly gate green; live MCP smoke (SDK client → node src/index.js) shows 76 tools (26 gf_ + 49 gv_ + gk_reload_abilities) and gv_layouts_list dispatches.
- gf_* tools are 'static, listed whenever Gravity Forms credentials are valid' (not unconditionally 'always available') — matches the now-gated tool advertising; note the two planes are independent. - GravityKit Product Tools: note they refuse remote plain-HTTP Basic auth unless GRAVITY_FORMS_ALLOW_HTTP_BASIC_AUTH=true (HTTPS/local always fine), matching the new WordPressClient guard.
|
@coderabbitai Review again, please |
|
✅ Action performedFull review finished. |
…names Confirmed against the live catalog + the gravityview-multiple-forms source: gk_apply_joins / gk_list_joins are NOT real tools. Multiple Forms registers abilities in the gk-multiple-forms namespace but declares no Foundation product / mcp_prefix, so those abilities have mcp_tool_name=null and the loader skips them (live tool set is 49 gv_*, zero join tools). They were a speculative code comment + a synthetic test fixture. - src/index.js: drop the made-up "gk_apply_joins, gk_list_joins" from the list_changed comment (those tools don't exist). - ability-catalog.mjs: collectAbilityNames default prefix gk-gravityview/ -> gk- so it covers every GravityKit product namespace, not just GravityView. (test/ability-catalog.test.js: RED→GREEN for a gk-multiple-forms name.) - verify-tool-names.mjs: ABIL_RE generalized to gk-<product>/; log/comment updated. TOOL_RE kept narrow (gf_/gv_ are the only prefixes surfacing real tools today) with a comment explaining why broadening risks false positives. Verified live: verify:tool-names 0 unknown across all surfaces; abilities now 52 (gk-gravityview 49 + gk-multiple-forms 3); test:node 166; lint:docs green.
Summary
Adds a second, independent capability plane to the MCP server and hardens the whole package for release.
gf_*), primary. 26 static tools over the GF REST API v2 (forms, entries, feeds, notifications, submissions, fields). Works on any Gravity Forms site.gv_*); other add-ons appear automatically. Built-ingk_reload_abilitiesrefetches the catalog.The two planes initialize and degrade independently. Much of the non-feature work here was surfaced by live-testing against a real WordPress site (minted with Siteminter) and an adversarial Codex review.
What's new — Plane B (the headline)
src/abilities/loader.js—loadAbilitiesAsTools()builds product tools from the live catalog: Foundation (/wp-json/gravitykit/v1/abilities) → WP-core (/wp-json/wp-abilities/v1/abilities) fallback → throw + self-heal. Server-owned tool names (each product'smcp_prefix); paginated.src/wp-client.js—WordPressClient, a product-agnostic authenticated WP transport (app-password Basic). Refuses Basic over a remote plain-HTTP URL unlessGRAVITY_FORMS_ALLOW_HTTP_BASIC_AUTH=true.tools.listChanged; per-call self-heal.src/gravityview/(inspector-client + view-validator) is a test/demo harness, not a runtime dependency.Plane A — auth + feeds
ck_/cs_keys → Basic on HTTPS, OAuth 1.0a on plain HTTP. OAuth signing fixed for array/nested query params.gf_list_feedstolerates a site with no feed add-on (GF'smissing_tableWP_Error →[]).Hardening (live testing + Codex review)
gf_*tools advertised only when the GF plane is live; call dispatch routes by ability-handler-map membership (any product prefix), not a hard-codedgv_check.gk_reload_abilities—gv_is GravityView's prefix only.layoutGridColumnSpan; non-empty/numberfield_id), paginated catalog read in the tool-name verifier, and assorted test-robustness fixes.Packaging
filesallowlist now ships runtime only (src minus tests,mcp.json,.env.example, README, LICENSE, CLAUDE.md, AGENTS.md). Deleted the dead.npmignore; tests relocated to top-leveltest/. Tarball 61 → 31 files (no tests/dev scripts).publint(npm run lint:package) + an offline doc-freshness guard (npm run lint:docs) wired intoprepublishOnly.package.json(src/version.js).Docs
CLAUDE.mdre-exports it via@AGENTS.md. Two-plane architecture documented; no fragilefile:linecitations (the doc guard enforces this and repo-map coverage).npx @gravitykit/mcpinstall/config, the GravityKit plane documented,gv_vs GravityKit prefix corrected.src/index.jsplane/list/dispatch logic extracted tosrc/server-runtime.jsso it's unit-testable.Tests
test:unit269 ·test:node165 ·test:views27 ·test:field-validation20 · publint · lint:docs — all green.verify:tool-names49/49 names match the catalog; MCP smoke (SDK client →node src/index.js) lists 76 tools (26gf_+ 49gv_+gk_reload_abilities) and dispatchesgv_*tools.Notes
mainalready merged in (ba6cc40).reports/(gitignored / untracked, not shipped).