@diviops/mcp-server 1.5.14 → 1.5.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -1
- package/data/verified-attrs-backlog.json +5 -5
- package/data/verified-attrs.json +8 -8
- package/dist/compatibility.d.ts +25 -0
- package/dist/index.js +349 -4
- package/dist/preset-cli/button-emitter.d.ts +1 -1
- package/dist/preset-cli/button-emitter.js +1 -1
- package/dist/preset-cli/cli.d.ts +4 -3
- package/dist/preset-cli/cli.js +11 -10
- package/dist/preset-cli/heading-font-emitter.d.ts +1 -1
- package/dist/preset-cli/heading-font-emitter.js +3 -3
- package/dist/preset-cli/spacing-emitter.d.ts +13 -13
- package/dist/preset-cli/spacing-emitter.js +23 -23
- package/dist/preset-cli/write-path.d.ts +3 -3
- package/dist/preset-cli/write-path.js +3 -3
- package/dist/wp-cli-fs-validator.d.ts +1 -1
- package/dist/wp-cli-fs-validator.js +1 -1
- package/dist/wp-cli.js +1 -2
- package/dist/wp-client.js +17 -0
- package/package.json +3 -2
- package/dist/preset-cli/__tests__/button-emitter.test.d.ts +0 -8
- package/dist/preset-cli/__tests__/button-emitter.test.js +0 -188
- package/dist/preset-cli/__tests__/cli.test.d.ts +0 -9
- package/dist/preset-cli/__tests__/cli.test.js +0 -791
- package/dist/preset-cli/__tests__/heading-font-emitter.test.d.ts +0 -12
- package/dist/preset-cli/__tests__/heading-font-emitter.test.js +0 -249
- package/dist/preset-cli/__tests__/preset-create-unchanged.test.d.ts +0 -13
- package/dist/preset-cli/__tests__/preset-create-unchanged.test.js +0 -64
- package/dist/preset-cli/__tests__/registry.test.d.ts +0 -5
- package/dist/preset-cli/__tests__/registry.test.js +0 -175
- package/dist/preset-cli/__tests__/spacing-emitter.test.d.ts +0 -20
- package/dist/preset-cli/__tests__/spacing-emitter.test.js +0 -409
- package/dist/preset-cli/__tests__/text-body-font-emitter.test.d.ts +0 -14
- package/dist/preset-cli/__tests__/text-body-font-emitter.test.js +0 -191
- package/dist/preset-cli/__tests__/write-path.test.d.ts +0 -8
- package/dist/preset-cli/__tests__/write-path.test.js +0 -287
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ The skill enforces the Divi block format, the design system, and the response co
|
|
|
64
64
|
|
|
65
65
|
## Tools at a glance
|
|
66
66
|
|
|
67
|
-
The server exposes **73 tools** across the categories below. Each category links to representative tools; the full table lives in [server-reference.md](../docs/server-reference.md).
|
|
67
|
+
The server exposes **73 always-on tools** across the categories below. Each category links to representative tools; the full table lives in [server-reference.md](../docs/server-reference.md).
|
|
68
68
|
|
|
69
69
|
| Category | Use case | Tool prefixes |
|
|
70
70
|
|----------|----------|---------------|
|
|
@@ -78,6 +78,15 @@ The server exposes **73 tools** across the categories below. Each category links
|
|
|
78
78
|
| WP-CLI passthrough | Escape hatch for site ops | `meta_wp_cli` |
|
|
79
79
|
| Cache + meta | Connection probe, identity, icons, cache flush | `meta_*` |
|
|
80
80
|
|
|
81
|
+
Additional **conditionally-registered Pro tools** appear only on sites that have the Pro plugin (`diviops-agent-pro`) active alongside the target coverage plugin:
|
|
82
|
+
|
|
83
|
+
| Category | Conditional gate | Tool names |
|
|
84
|
+
|----------|------------------|------------|
|
|
85
|
+
| FluentCart reads (V1) | Pro plugin + FluentCart installed + module enabled | `diviops_fc_product_list`, `diviops_fc_product_get` |
|
|
86
|
+
| FluentCart simple product writes (V2) | Pro plugin + FluentCart installed + module enabled | `diviops_fc_product_create`, `diviops_fc_product_update`, `diviops_fc_product_delete` |
|
|
87
|
+
|
|
88
|
+
When the gates are not satisfied, the tools simply don't appear on the MCP surface — no error envelope, no missing-capability hint. See the `diviops-fluentcart` skill bundle for the operator-side guide.
|
|
89
|
+
|
|
81
90
|
See [server-reference.md](../docs/server-reference.md) for per-tool descriptions.
|
|
82
91
|
|
|
83
92
|
## Bundled CLI — `diviops-preset`
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"blocker": "Pattern is VB_PRESET_STORAGE_VERIFIED (round-4-section.json) but applicability[divi/heading].cell_evidence_level is SCHEMA_OBSERVED. Need a divi/heading module-preset capture that emits module.decoration.background.desktop.value.color to lift the cell.",
|
|
14
14
|
"capture_target": "module-preset on divi/heading with module.decoration.background.desktop.value.color set via VB",
|
|
15
15
|
"value_to_authoring": "high (heading-with-background is a common pattern for accent / callout headings)",
|
|
16
|
-
"notes": "Should be captured on
|
|
16
|
+
"notes": "Should be captured on the canonical clean reference substrate per reference_substrate_roles."
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"priority": 1,
|
|
@@ -85,14 +85,14 @@
|
|
|
85
85
|
"priority": 2,
|
|
86
86
|
"pattern_family": "divi/spacing",
|
|
87
87
|
"current_effective_evidence": "SCHEMA_OBSERVED",
|
|
88
|
-
"blocker": "
|
|
88
|
+
"blocker": "The canonical section spacing capture (2026-05-23) captured divi/spacing group-preset bound to divi/section on Divi 5.6.0, with both padding and margin axes confirmed sparse-emit + always-paired-sync-flags shape. Pattern level + divi/section cell promoted to VB_PRESET_STORAGE_VERIFIED. Heading/text/button cells still need their own roundtrips on the canonical clean reference substrate — each module's per-wrapper spacing cell may have its own shape quirks per feedback_preset_map_per_module, AND the group_id value (currently schema-inferred as 'title.decoration.spacing' / 'content.decoration.spacing' / 'module.decoration.spacing') is now KNOWN-MAYBE-WRONG since the section cell uses groupId='designSpacing' (Composable Settings panel id, not dotted attr path); per-cell capture must verify whether each cell uses the same value or a wrapper-prefixed variant.",
|
|
89
89
|
"capture_targets": [
|
|
90
|
-
"divi/spacing group preset bound to title wrapper on divi/heading (proper capture on
|
|
91
|
-
"divi/spacing group preset bound to content wrapper on divi/text (proper capture on
|
|
90
|
+
"divi/spacing group preset bound to title wrapper on divi/heading (proper capture on the canonical clean reference substrate; do NOT use sandbox preset ecs423stpr or yjh59i25br as evidence — they are unverified residue from prior exploration)",
|
|
91
|
+
"divi/spacing group preset bound to content wrapper on divi/text (proper capture on the canonical clean reference substrate)",
|
|
92
92
|
"divi/spacing group preset bound to module wrapper on divi/button (Button exception path — Button uses module.decoration.spacing per Button exception, not button.decoration.spacing which is VB-hidden)"
|
|
93
93
|
],
|
|
94
94
|
"value_to_authoring": "very high (Composable Settings spacing presets are the entire preset-driven authoring story for spacing)",
|
|
95
|
-
"notes": "Pattern-level already promoted via
|
|
95
|
+
"notes": "Pattern-level already promoted via the canonical section spacing capture. Remaining captures lift only their per-module cells (no further pattern-level promotion needed). Each capture must independently verify groupId — do NOT assume 'designSpacing' propagates from the section cell. Note: the write/reset sandbox substrate has three pre-existing divi/spacing presets (g4zpdl2v6g, ecs423stpr, yjh59i25br) that visually carry sparse-emit shape including title-wrapper variants, but these are unverified residue; treat as 'question is approachable' only, not as evidence."
|
|
96
96
|
},
|
|
97
97
|
{
|
|
98
98
|
"priority": 2,
|
package/data/verified-attrs.json
CHANGED
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
"Tier 3 = module-specific unique attrs. Per-module cell-level only.",
|
|
19
19
|
"Group-preset routing fields (group_name + group_id) appear per applicability cell on preset_type:group entries because shared buckets (e.g. divi/spacing) have constant groupName across modules but divergent groupId per use.",
|
|
20
20
|
"styleAttrs shorthand contract: capture files may use shorthand strings (e.g. 'byte-identical to attrs'); treat semantically as deepClone(attrs). Do not rewrite captures.",
|
|
21
|
-
"Day-one allowlist is a DERIVED QUERY at
|
|
21
|
+
"Day-one allowlist is a DERIVED QUERY at preset-CLI runtime: filter entries whose effective evidence (min rule above) is >= VB_PRESET_STORAGE_VERIFIED. No separate allowlist artifact ships.",
|
|
22
22
|
"Pattern A (Google Fonts: separate family + weight) and Pattern B (local-hosted: weight encoded in family string) are distinct entries; write emitter MUST NOT vouch one with the other's evidence.",
|
|
23
|
-
"Sources point to durable locations only: committed files in this repo (docs/, .claude/skills/) or stable URLs.
|
|
23
|
+
"Sources point to durable locations only: committed files in this repo (docs/, .claude/skills/) or stable URLs. Gitignored draft paths are forbidden."
|
|
24
24
|
],
|
|
25
25
|
"tier1": [
|
|
26
26
|
{
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"pattern_evidence_source": "docs/verification/evidence/canonical-shape-dumps-2026-05-18/round-4-section.json",
|
|
78
78
|
"pattern_divi_version": "5.5.2",
|
|
79
79
|
"pattern_verified_at": "2026-05-18",
|
|
80
|
-
"canonical_shape_note": "module.decoration.spacing.desktop.value.padding (and .margin) is sparse-emit: only user-touched corners (top, right, bottom, left) appear in attrs, plus syncVertical + syncHorizontal which BOTH always emit on any spacing touch (atomic sibling pair — both engage together even when only one direction was touched). Sync flag values are 'off' by default, 'on' when user explicitly enables sync. Round-4 section main capture (top+bottom touched) stored {top, bottom, syncVertical, syncHorizontal} — no left/right. Round-4 discriminator (top+bottom only, sync explicitly NOT touched) confirms sync flags engage atomically while corners do not. Phase 2 emitter rule: emit only the specified corners + always emit both sync flags. Atomicity claim revised 2026-05-23 per
|
|
80
|
+
"canonical_shape_note": "module.decoration.spacing.desktop.value.padding (and .margin) is sparse-emit: only user-touched corners (top, right, bottom, left) appear in attrs, plus syncVertical + syncHorizontal which BOTH always emit on any spacing touch (atomic sibling pair — both engage together even when only one direction was touched). Sync flag values are 'off' by default, 'on' when user explicitly enables sync. Round-4 section main capture (top+bottom touched) stored {top, bottom, syncVertical, syncHorizontal} — no left/right. Round-4 discriminator (top+bottom only, sync explicitly NOT touched) confirms sync flags engage atomically while corners do not. Phase 2 emitter rule: emit only the specified corners + always emit both sync flags. Atomicity claim revised 2026-05-23 per the canonical section spacing group-bucket capture which corroborates the same sparse-emit + always-paired-sync shape on the group-preset path (evidence: docs/verification/evidence/canonical-shape-dumps-2026-05-23/round-5-spacing-section.json).",
|
|
81
81
|
"applicability": {
|
|
82
82
|
"divi/section": {
|
|
83
83
|
"wrapper": "module",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"source": "docs/verification/evidence/canonical-shape-dumps-2026-05-18/round-4-section.json",
|
|
89
89
|
"preset_map_key": "module.decoration.spacing__padding",
|
|
90
90
|
"state": "desktop.value",
|
|
91
|
-
"caveats": ["Padding (and margin) is sparse-emit: corners appear ONLY when user touched them; syncVertical + syncHorizontal always emit on any spacing touch (atomic sibling pair). Phase 2 emitter rule: emit only the specified corners + always emit both sync flags. Default sync values are 'off'; 'on' when user explicitly enables sync. Atomicity claim revised 2026-05-23 per
|
|
91
|
+
"caveats": ["Padding (and margin) is sparse-emit: corners appear ONLY when user touched them; syncVertical + syncHorizontal always emit on any spacing touch (atomic sibling pair). Phase 2 emitter rule: emit only the specified corners + always emit both sync flags. Default sync values are 'off'; 'on' when user explicitly enables sync. Atomicity claim revised 2026-05-23 per the canonical section spacing capture evidence."]
|
|
92
92
|
},
|
|
93
93
|
"divi/heading": {
|
|
94
94
|
"wrapper": "module",
|
|
@@ -499,7 +499,7 @@
|
|
|
499
499
|
"pattern_evidence_source": "docs/verification/evidence/canonical-shape-dumps-2026-05-23/round-5-spacing-section.json",
|
|
500
500
|
"pattern_divi_version": "5.6.0",
|
|
501
501
|
"pattern_verified_at": "2026-05-23",
|
|
502
|
-
"canonical_shape_note": "Composable Settings spacing bucket (group preset). VB-verified on Divi 5.6.0 via
|
|
502
|
+
"canonical_shape_note": "Composable Settings spacing bucket (group preset). VB-verified on Divi 5.6.0 via the canonical section spacing capture (divi/section cell only): groupId is the Composable Settings panel id 'designSpacing' (NOT a dotted attribute path — prior 'module.decoration.spacing' note was a misread). primaryAttrName is 'module' for section spacing; registry schema does not carry primaryAttrName today, recorded in the divi/section cell caveats instead. Attrs root: attrs.<primaryAttrName>.decoration.spacing.desktop.value.{padding|margin}, where both padding and margin follow the IDENTICAL sparse-emit rule: only user-touched corners (top, right, bottom, left) appear, with syncVertical + syncHorizontal always emitted together on any spacing touch (atomic sibling pair; 'off' by default, 'on' when user explicitly enables sync). Margin capture (kxc3elho8v on the write/reset sandbox substrate) corroborates the same shape rule with single-corner touch on the margin axis. Round-trip render-side verification (divi/section block referencing an MCP-created preset on the write/reset sandbox substrate, page 1520): clean — empty corners stay empty in VB, no phantom defaults. Inner-element variants (title, content wrappers) and non-section module cells (heading, text, button) are NOT verified — they retain schema-inferred group_id values with caveats; do NOT extrapolate this section evidence to those cells per feedback_preset_map_per_module.",
|
|
503
503
|
"applicability": {
|
|
504
504
|
"divi/heading": {
|
|
505
505
|
"wrapper": "title",
|
|
@@ -512,7 +512,7 @@
|
|
|
512
512
|
"source": ".claude/skills/divi-5-builder/references/presets.md#known-group-bucket-inventory-vb-verified-2026-05-04",
|
|
513
513
|
"caveats": [
|
|
514
514
|
"divi/spacing group bucket VB-confirmed (5.4.0); per-module title-spacing cell needs its own roundtrip",
|
|
515
|
-
"group_id is NOT VB-verified for this cell — value 'title.decoration.spacing' is schema-inferred.
|
|
515
|
+
"group_id is NOT VB-verified for this cell — value 'title.decoration.spacing' is schema-inferred. The canonical section spacing capture (5.6.0) proved the section cell uses groupId='designSpacing' (Composable Settings panel id, not dotted attr path); whether heading uses the same value or a wrapper-prefixed variant is unverified. Do NOT use this group_id for preset writes until a heading-spacing capture on the canonical clean reference substrate lands. Note: write/reset sandbox presets (ids ecs423stpr, yjh59i25br) are bound to title wrapper with the same sparse-emit shape, but these are unverified residue and explicitly NOT used as evidence; proper capture on the canonical reference substrate is required."
|
|
516
516
|
]
|
|
517
517
|
},
|
|
518
518
|
"divi/text": {
|
|
@@ -525,7 +525,7 @@
|
|
|
525
525
|
"verified_at": "2026-05-19",
|
|
526
526
|
"source": ".claude/skills/divi-5-builder/references/presets.md#known-group-bucket-inventory-vb-verified-2026-05-04",
|
|
527
527
|
"caveats": [
|
|
528
|
-
"group_id is NOT VB-verified for this cell — value 'content.decoration.spacing' is schema-inferred.
|
|
528
|
+
"group_id is NOT VB-verified for this cell — value 'content.decoration.spacing' is schema-inferred. The canonical section spacing capture (5.6.0) proved the section cell uses groupId='designSpacing' (Composable Settings panel id, not dotted attr path); whether text uses the same value or a wrapper-prefixed variant is unverified. Do NOT use this group_id for preset writes until a text-spacing capture on the canonical clean reference substrate lands."
|
|
529
529
|
]
|
|
530
530
|
},
|
|
531
531
|
"divi/section": {
|
|
@@ -556,7 +556,7 @@
|
|
|
556
556
|
"source": ".claude/skills/divi-5-builder/references/presets.md#known-group-bucket-inventory-vb-verified-2026-05-04",
|
|
557
557
|
"caveats": [
|
|
558
558
|
"Button padding is canonically authored on module.decoration.spacing per Button exception (NOT button.decoration.spacing which is VB-hidden)",
|
|
559
|
-
"group_id is NOT VB-verified for this cell — value 'module.decoration.spacing' is schema-inferred.
|
|
559
|
+
"group_id is NOT VB-verified for this cell — value 'module.decoration.spacing' is schema-inferred. The canonical section spacing capture (5.6.0) proved the section cell uses groupId='designSpacing' (Composable Settings panel id, not dotted attr path); whether button uses the same value or differs is unverified. Do NOT use this group_id for preset writes until a button-spacing capture on the canonical clean reference substrate lands."
|
|
560
560
|
]
|
|
561
561
|
}
|
|
562
562
|
}
|
package/dist/compatibility.d.ts
CHANGED
|
@@ -27,6 +27,17 @@
|
|
|
27
27
|
* Pre-release versions (e.g. 1.0.0-beta.22) sort before their release (1.0.0).
|
|
28
28
|
*/
|
|
29
29
|
export declare function compareVersions(a: string, b: string): -1 | 0 | 1;
|
|
30
|
+
/**
|
|
31
|
+
* Per-target presence record (ADR-003 § handshake shape). `present`
|
|
32
|
+
* reports whether the target plugin is installed + bootable on this
|
|
33
|
+
* site; `version` is advisory only and may be null when version
|
|
34
|
+
* detection is unavailable (front-end requests can't always reach
|
|
35
|
+
* `get_plugin_data()`).
|
|
36
|
+
*/
|
|
37
|
+
export interface HandshakeTarget {
|
|
38
|
+
present: boolean;
|
|
39
|
+
version?: string | null;
|
|
40
|
+
}
|
|
30
41
|
/**
|
|
31
42
|
* Shape returned by `POST /diviops/v1/handshake`.
|
|
32
43
|
*
|
|
@@ -35,6 +46,12 @@ export declare function compareVersions(a: string, b: string): -1 | 0 | 1;
|
|
|
35
46
|
* a string[] of coarse namespace keys; the server normalizes that
|
|
36
47
|
* legacy shape to an empty map (every gated tool then fails fast
|
|
37
48
|
* with an upgrade hint, which is the intended behavior).
|
|
49
|
+
*
|
|
50
|
+
* ADR-003 / ADR-007 Pro-extension fields (`pro_active`,
|
|
51
|
+
* `pro_version`, `available_targets`, `active_modules`) are
|
|
52
|
+
* optional — Free-only sites omit them entirely. The server treats
|
|
53
|
+
* absence as `pro_active: false` + empty target/module maps so
|
|
54
|
+
* Pro-gated tools cleanly decline registration on Free sites.
|
|
38
55
|
*/
|
|
39
56
|
export interface HandshakeResult {
|
|
40
57
|
compatible: boolean;
|
|
@@ -45,6 +62,14 @@ export interface HandshakeResult {
|
|
|
45
62
|
version: string | null;
|
|
46
63
|
};
|
|
47
64
|
capabilities: Record<string, boolean>;
|
|
65
|
+
/** ADR-007 § 7.1 — Pro plugin presence flag. Undefined on Free sites. */
|
|
66
|
+
pro_active?: boolean;
|
|
67
|
+
/** ADR-003 — Pro plugin version (when `pro_active === true`). */
|
|
68
|
+
pro_version?: string;
|
|
69
|
+
/** ADR-003 — per-target presence map (FluentCart, future slices). */
|
|
70
|
+
available_targets?: Record<string, HandshakeTarget>;
|
|
71
|
+
/** ADR-003 — per-target admin-controlled activation toggle. */
|
|
72
|
+
active_modules?: Record<string, boolean>;
|
|
48
73
|
}
|
|
49
74
|
/**
|
|
50
75
|
* Thrown when a tool handler calls `requireCapability(key)` and the
|
package/dist/index.js
CHANGED
|
@@ -136,6 +136,58 @@ function registerLocalTool(name, config, handler) {
|
|
|
136
136
|
recordIdempotent(name, config?._meta);
|
|
137
137
|
server.registerTool(name, config, handler);
|
|
138
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Register a Pro-coverage-slice tool (ADR-003 / ADR-007).
|
|
141
|
+
*
|
|
142
|
+
* Differs from `registerPluginTool` in three ways:
|
|
143
|
+
*
|
|
144
|
+
* 1. **Capability-key override.** The MCP tool name follows the
|
|
145
|
+
* `diviops_<namespace>_<verb>` convention (e.g. `diviops_fc_product_list`),
|
|
146
|
+
* while the plugin-side capability key follows ADR-007's
|
|
147
|
+
* `<target>_<noun>_<verb>` shape (e.g. `fluentcart_product_list`).
|
|
148
|
+
* The two don't share a stripping rule, so the capability key must
|
|
149
|
+
* be passed explicitly.
|
|
150
|
+
*
|
|
151
|
+
* 2. **Conditional registration.** The tool is registered with the
|
|
152
|
+
* MCP server ONLY when all four gates align at handshake time:
|
|
153
|
+
* - handshakeState.kind === "ok"
|
|
154
|
+
* - proActive === true
|
|
155
|
+
* - availableTargets[target].present === true
|
|
156
|
+
* - activeModules[target] === true
|
|
157
|
+
* - capabilities[capabilityKey] === true
|
|
158
|
+
*
|
|
159
|
+
* When any gate is false the call is a no-op — the tool simply
|
|
160
|
+
* doesn't exist on the MCP surface. Per ADR-007 "no error surface,
|
|
161
|
+
* just absence."
|
|
162
|
+
*
|
|
163
|
+
* 3. **No runtime requireCapability().** Because registration is
|
|
164
|
+
* already gated at startup, the wrapped handler doesn't need to
|
|
165
|
+
* recheck capabilities on every call. The wp.request() call is
|
|
166
|
+
* still naturally guarded by the plugin's permission_callback +
|
|
167
|
+
* route presence at the WP side.
|
|
168
|
+
*
|
|
169
|
+
* **Call-site ordering.** This helper MUST be invoked from
|
|
170
|
+
* `registerProTools()` (run after the handshake settles in `main()`),
|
|
171
|
+
* not at module load time. Calling it at module load would always
|
|
172
|
+
* short-circuit on `handshakeState.kind === "pending"`. The Pro tools
|
|
173
|
+
* are defined inside `registerProTools()` precisely so they can read
|
|
174
|
+
* the resolved handshakeState.
|
|
175
|
+
*/
|
|
176
|
+
function registerProTool(name, config, handler, gates) {
|
|
177
|
+
if (handshakeState.kind !== "ok")
|
|
178
|
+
return;
|
|
179
|
+
if (!handshakeState.proActive)
|
|
180
|
+
return;
|
|
181
|
+
const target = handshakeState.availableTargets[gates.target];
|
|
182
|
+
if (!target || target.present !== true)
|
|
183
|
+
return;
|
|
184
|
+
if (handshakeState.activeModules[gates.target] !== true)
|
|
185
|
+
return;
|
|
186
|
+
if (!handshakeState.capabilities[gates.capabilityKey])
|
|
187
|
+
return;
|
|
188
|
+
recordIdempotent(name, config?._meta);
|
|
189
|
+
server.registerTool(name, config, handler);
|
|
190
|
+
}
|
|
139
191
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
140
192
|
// ── dry_run convention ──────────────────────────────────────────────
|
|
141
193
|
//
|
|
@@ -2999,21 +3051,308 @@ registerPluginTool("diviops_meta_flush_cache", {
|
|
|
2999
3051
|
],
|
|
3000
3052
|
};
|
|
3001
3053
|
});
|
|
3054
|
+
// ── Pro coverage-slice tools (ADR-003 / ADR-007) ─────────────────────
|
|
3055
|
+
//
|
|
3056
|
+
// FCP V1 read tools — ADR-007 § 7.1. Registered through `registerProTool`
|
|
3057
|
+
// which short-circuits when any of {pro_active, target presence, module
|
|
3058
|
+
// activation, capability key} gates are false. On Free-only sites the
|
|
3059
|
+
// FCP tools simply don't exist on the MCP surface — no error envelope,
|
|
3060
|
+
// no missing-capability hint, just absence.
|
|
3061
|
+
//
|
|
3062
|
+
// Run inside `registerProTools()` rather than at module load because the
|
|
3063
|
+
// gates read handshakeState which is `pending` until `main()` runs.
|
|
3064
|
+
function registerProTools() {
|
|
3065
|
+
// diviops_fc_product_list — bridges /diviops/v1/pro/fluentcart/products
|
|
3066
|
+
registerProTool("diviops_fc_product_list", {
|
|
3067
|
+
description: "List FluentCart Pro products (Pro tier; requires FluentCart Pro installed + activated). Returns a paginated summary list with product identity (id, title, slug, status), variation_type, variants_count, and min/max price. Filterable by `search` (LIKE post_title), `type` (one of physical/digital/subscription/onetime/simple/variations), and `status` (one of publish/draft/pending/private/trash; default returns publish+draft+pending+private). Read-only. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; the success payload is { products: ProductSummary[], pagination: { page, per_page, total, total_pages }, filters: { search, type, status } }. Error codes: invalid_input (HTTP 400) when type/status filter is out of range; fluentcart.module_inactive (HTTP 412) when FluentCart is uninstalled or the diviops-agent-pro module toggle is off; fluentcart.query_failed (HTTP 500) when the underlying FluentCart model query raises an exception (message field carries the upstream exception). Use this before authoring a Divi commerce page to identify which product IDs / types to render.",
|
|
3068
|
+
inputSchema: {
|
|
3069
|
+
page: z
|
|
3070
|
+
.number()
|
|
3071
|
+
.int()
|
|
3072
|
+
.positive()
|
|
3073
|
+
.optional()
|
|
3074
|
+
.default(1)
|
|
3075
|
+
.describe("Page number, 1-indexed. Default 1."),
|
|
3076
|
+
per_page: z
|
|
3077
|
+
.number()
|
|
3078
|
+
.int()
|
|
3079
|
+
.positive()
|
|
3080
|
+
.optional()
|
|
3081
|
+
.default(20)
|
|
3082
|
+
.describe("Page size. Default 20, clamped to a max of 100 per call."),
|
|
3083
|
+
search: z
|
|
3084
|
+
.string()
|
|
3085
|
+
.optional()
|
|
3086
|
+
.describe("Search term — matches against product post_title via SQL LIKE %term%. Case-insensitive on most MySQL collations."),
|
|
3087
|
+
type: z
|
|
3088
|
+
.enum([
|
|
3089
|
+
"physical",
|
|
3090
|
+
"digital",
|
|
3091
|
+
"subscription",
|
|
3092
|
+
"onetime",
|
|
3093
|
+
"simple",
|
|
3094
|
+
"variations",
|
|
3095
|
+
])
|
|
3096
|
+
.optional()
|
|
3097
|
+
.describe("Product type filter. physical/digital filter by fulfillment_type on variations; subscription/onetime filter by payment_type on variations; simple filters detail.variation_type='simple'; variations filters detail.variation_type in {simple_variations, advanced_variations}."),
|
|
3098
|
+
status: z
|
|
3099
|
+
.enum(["publish", "draft", "pending", "private", "trash"])
|
|
3100
|
+
.optional()
|
|
3101
|
+
.describe("Post status filter. Defaults to all visible-to-admin statuses (publish+draft+pending+private). Pass 'trash' explicitly to inspect trashed products."),
|
|
3102
|
+
},
|
|
3103
|
+
annotations: { idempotentHint: true },
|
|
3104
|
+
_meta: { idempotent: "true" },
|
|
3105
|
+
}, async ({ page, per_page, search, type, status, }) => {
|
|
3106
|
+
const body = {};
|
|
3107
|
+
if (page !== undefined)
|
|
3108
|
+
body.page = page;
|
|
3109
|
+
if (per_page !== undefined)
|
|
3110
|
+
body.per_page = per_page;
|
|
3111
|
+
if (search !== undefined)
|
|
3112
|
+
body.search = search;
|
|
3113
|
+
if (type !== undefined)
|
|
3114
|
+
body.type = type;
|
|
3115
|
+
if (status !== undefined)
|
|
3116
|
+
body.status = status;
|
|
3117
|
+
const result = await wp.requestEnveloped("/pro/fluentcart/products", { method: "POST", body });
|
|
3118
|
+
return {
|
|
3119
|
+
content: [
|
|
3120
|
+
{
|
|
3121
|
+
type: "text",
|
|
3122
|
+
text: serializeEnvelope(result, "diviops_fc_product_list"),
|
|
3123
|
+
},
|
|
3124
|
+
],
|
|
3125
|
+
};
|
|
3126
|
+
}, { target: "fluentcart", capabilityKey: "fluentcart_product_list" });
|
|
3127
|
+
// diviops_fc_product_get — bridges /diviops/v1/pro/fluentcart/products/{id}
|
|
3128
|
+
registerProTool("diviops_fc_product_get", {
|
|
3129
|
+
description: "Fetch a single FluentCart Pro product by ID, including the ProductDetail row, the default-variation read-back fields, and a list of variation IDs (Pro tier; requires FluentCart Pro installed + activated). Read-only. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; the success payload is { product: { id, title, slug, status, created_at, modified_at, variation_type, variants_count, min_price, max_price, stock_availability, excerpt, content, author_id, view_url, edit_url }, detail: { fulfillment_type, variation_type, min_price, max_price, manage_stock, manage_downloadable, stock_availability, default_variation_id, ... } | null, default_variation: { id, sku, item_price, compare_price } | null, variation_ids: number[], variations_count }. The default_variation block closes the read-after-write loop for the V2 simple-product write tools — sku and compare_price round-trip cleanly without a FluentCart admin fallback. Unit asymmetry: product_create / product_update accept price and compare_price in currency units (e.g. 29.99); product_get returns min_price, max_price, default_variation.item_price, and default_variation.compare_price in stored cents (e.g. 2999). default_variation.sku is null when the stored SKU is SQL NULL; clearing a SKU with sku: \"\" on update reads back as null. default_variation itself is null when the product has no default variation row. Use the variation_ids list to follow up with a (future) diviops_fc_variation_list call. Error codes: invalid_input (HTTP 400) when id is not a positive integer; not_found (HTTP 404) when no product matches the ID (or it's filtered out by the FluentCart auto-draft global scope); fluentcart.module_inactive (HTTP 412) when FluentCart is uninstalled or the module toggle is off; fluentcart.query_failed (HTTP 500) when the FluentCart model query raises an exception.",
|
|
3130
|
+
inputSchema: {
|
|
3131
|
+
id: z
|
|
3132
|
+
.number()
|
|
3133
|
+
.int()
|
|
3134
|
+
.positive()
|
|
3135
|
+
.describe("FluentCart product ID (the post ID of the fluent_products CPT entry)."),
|
|
3136
|
+
},
|
|
3137
|
+
annotations: { idempotentHint: true },
|
|
3138
|
+
_meta: { idempotent: "true" },
|
|
3139
|
+
}, async ({ id }) => {
|
|
3140
|
+
const result = await wp.requestEnveloped(`/pro/fluentcart/products/${id}`, { method: "POST" });
|
|
3141
|
+
return {
|
|
3142
|
+
content: [
|
|
3143
|
+
{
|
|
3144
|
+
type: "text",
|
|
3145
|
+
text: serializeEnvelope(result, "diviops_fc_product_get"),
|
|
3146
|
+
},
|
|
3147
|
+
],
|
|
3148
|
+
};
|
|
3149
|
+
}, { target: "fluentcart", capabilityKey: "fluentcart_product_get" });
|
|
3150
|
+
// ── V2 — simple product writes ─────────────────────────────────────
|
|
3151
|
+
//
|
|
3152
|
+
// Three Pro write tools backing the constrained simple-onetime-product
|
|
3153
|
+
// surface from ADR-007 § 7.1. All three accept `dry_run` (default
|
|
3154
|
+
// false), emit the standard envelope, and refuse non-simple shapes
|
|
3155
|
+
// with `fluentcart.unsupported_product_shape` so the V3 variation
|
|
3156
|
+
// surface can own multi-variant complexity cleanly.
|
|
3157
|
+
// diviops_fc_product_create — POST /diviops/v1/pro/fluentcart/products/create
|
|
3158
|
+
registerProTool("diviops_fc_product_create", {
|
|
3159
|
+
description: "Create a simple FluentCart Pro product (Pro tier; requires FluentCart Pro installed + activated). V2 scope: simple onetime products only — one default variant, `detail.variation_type=\"simple\"`, `payment_type=\"onetime\"`, `fulfillment_type=\"digital\"|\"physical\"`. Multi-variation, subscriptions, downloadables, gallery, taxonomies, activation_limit, and license-flow fields ship in later verticals and are refused here. Required: `title` (1-200 chars). Optional: `status` (`draft`|`publish`|`pending`|`private`; default `draft`), `content`, `excerpt`, `fulfillment_type` (default `digital`), `price` (≥0; default 0), `compare_price` (≥0; must be ≥ `price` when provided), `sku` (unique across variations). Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; apply-mode success payload is { product, detail, default_variation: { id, sku, item_price, compare_price } | null, variation_ids, variations_count, product_id, detail_id, default_variation_id } (HTTP 201). The default_variation block mirrors the diviops_fc_product_get read-back shape so callers don't need a follow-up read to confirm sku / compare_price / item_price after a write. Unit asymmetry: write inputs (price, compare_price) are in currency units (e.g. 29.99); the default_variation block returns item_price + compare_price in stored cents (e.g. 2999), matching detail.min_price / max_price. default_variation.compare_price is null when no compare price is stored (FCP persists \"no compare\" as 0, normalized to null on read); default_variation.sku is null when the SKU column is SQL NULL or an empty string (the create path stores omitted SKU as NULL). Error codes: invalid_input (400) when any input violates the constraints above; fluentcart.sku_conflict (409) when the provided SKU is already in use; fluentcart.module_inactive (412); fluentcart.command_failed (500) when wp_insert_post/ProductDetail/ProductVariation creation raises. Idempotency: NOT idempotent — repeat calls create distinct products." +
|
|
3160
|
+
DRY_RUN_DESC_SUFFIX,
|
|
3161
|
+
inputSchema: {
|
|
3162
|
+
title: z
|
|
3163
|
+
.string()
|
|
3164
|
+
.min(1)
|
|
3165
|
+
.max(200)
|
|
3166
|
+
.describe("Product title (post_title). 1-200 chars. Used verbatim as the default variation's variation_title."),
|
|
3167
|
+
status: z
|
|
3168
|
+
.enum(["draft", "publish", "pending", "private"])
|
|
3169
|
+
.optional()
|
|
3170
|
+
.describe("Post status. Defaults to 'draft'."),
|
|
3171
|
+
content: z
|
|
3172
|
+
.string()
|
|
3173
|
+
.optional()
|
|
3174
|
+
.describe("Long product description (post_content). Optional."),
|
|
3175
|
+
excerpt: z
|
|
3176
|
+
.string()
|
|
3177
|
+
.optional()
|
|
3178
|
+
.describe("Short product summary (post_excerpt). Optional."),
|
|
3179
|
+
fulfillment_type: z
|
|
3180
|
+
.enum(["digital", "physical"])
|
|
3181
|
+
.optional()
|
|
3182
|
+
.describe("Fulfillment shape — digital downloads vs physical shipping. Defaults to 'digital'."),
|
|
3183
|
+
price: z
|
|
3184
|
+
.number()
|
|
3185
|
+
.min(0)
|
|
3186
|
+
.optional()
|
|
3187
|
+
.describe("Default variation's item_price (currency units, e.g. dollars — converted to cents server-side). Non-negative. Defaults to 0."),
|
|
3188
|
+
compare_price: z
|
|
3189
|
+
.number()
|
|
3190
|
+
.min(0)
|
|
3191
|
+
.optional()
|
|
3192
|
+
.describe("Default variation's compare-at price (strike-through). Must be ≥ `price` when both provided. Non-negative."),
|
|
3193
|
+
sku: z
|
|
3194
|
+
.string()
|
|
3195
|
+
.optional()
|
|
3196
|
+
.describe("Default variation's SKU. Must be unique across all FluentCart variations. Omit to skip SKU assignment."),
|
|
3197
|
+
dry_run: DRY_RUN_FIELD,
|
|
3198
|
+
},
|
|
3199
|
+
annotations: { idempotentHint: false },
|
|
3200
|
+
_meta: { idempotent: "false" },
|
|
3201
|
+
}, async ({ title, status, content, excerpt, fulfillment_type, price, compare_price, sku, dry_run, }) => {
|
|
3202
|
+
const body = { title };
|
|
3203
|
+
if (status !== undefined)
|
|
3204
|
+
body.status = status;
|
|
3205
|
+
if (content !== undefined)
|
|
3206
|
+
body.content = content;
|
|
3207
|
+
if (excerpt !== undefined)
|
|
3208
|
+
body.excerpt = excerpt;
|
|
3209
|
+
if (fulfillment_type !== undefined)
|
|
3210
|
+
body.fulfillment_type = fulfillment_type;
|
|
3211
|
+
if (price !== undefined)
|
|
3212
|
+
body.price = price;
|
|
3213
|
+
if (compare_price !== undefined)
|
|
3214
|
+
body.compare_price = compare_price;
|
|
3215
|
+
if (sku !== undefined)
|
|
3216
|
+
body.sku = sku;
|
|
3217
|
+
if (dry_run !== undefined)
|
|
3218
|
+
body.dry_run = dry_run;
|
|
3219
|
+
const result = await wp.requestEnveloped("/pro/fluentcart/products/create", { method: "POST", body });
|
|
3220
|
+
return {
|
|
3221
|
+
content: [
|
|
3222
|
+
{
|
|
3223
|
+
type: "text",
|
|
3224
|
+
text: serializeEnvelope(result, "diviops_fc_product_create"),
|
|
3225
|
+
},
|
|
3226
|
+
],
|
|
3227
|
+
};
|
|
3228
|
+
}, { target: "fluentcart", capabilityKey: "fluentcart_product_create" });
|
|
3229
|
+
// diviops_fc_product_update — POST /diviops/v1/pro/fluentcart/products/{id}/update
|
|
3230
|
+
registerProTool("diviops_fc_product_update", {
|
|
3231
|
+
description: "Update a simple FluentCart Pro product (Pro tier; requires FluentCart Pro installed + activated). V2 scope: simple onetime products only — accepts partial updates on title, status, content, excerpt, fulfillment_type, price, compare_price, sku. Refuses non-simple products (variation_type other than 'simple', or default variant with payment_type other than 'onetime') with `fluentcart.unsupported_product_shape` (HTTP 422) — multi-variation + subscription writes ship in V3+. Required: `id` (positive integer; the post ID of the fluent_products CPT entry). All other fields optional; only changed fields are applied. When no field actually changes, returns `ok:true` with `data.noop: true` (apply mode) or an empty-plan dry-run summary. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; apply-mode success payload is { product, detail, default_variation: { id, sku, item_price, compare_price } | null, variation_ids, variations_count, changed_fields[] } (or { noop: true, product, detail, default_variation, ... } on a no-op). The default_variation block reflects the post-update state (or current state on noop) and mirrors the diviops_fc_product_get shape — sku, item_price, and compare_price round-trip without a follow-up read. Unit asymmetry: write inputs (price, compare_price) are in currency units (e.g. 29.99); default_variation returns item_price + compare_price in stored cents (e.g. 2999). default_variation.sku is null after `sku: \"\"` clears it (the empty string is stored as SQL NULL so the variations table's UNIQUE constraint allows multiple cleared SKUs); default_variation.compare_price is null when no compare price is stored (FCP persists \"no compare\" as 0, normalized to null on read). Error codes: invalid_input (400) when any field violates the constraints; not_found (404) when the product ID does not exist; fluentcart.unsupported_product_shape (422) when the product is not simple/onetime; fluentcart.sku_conflict (409) when a new SKU collides with another variation; fluentcart.module_inactive (412); fluentcart.command_failed (500). Idempotency: conditional — repeating an identical update is a no-op." +
|
|
3232
|
+
DRY_RUN_DESC_SUFFIX,
|
|
3233
|
+
inputSchema: {
|
|
3234
|
+
id: z
|
|
3235
|
+
.number()
|
|
3236
|
+
.int()
|
|
3237
|
+
.positive()
|
|
3238
|
+
.describe("FluentCart product ID (the post ID of the fluent_products CPT entry)."),
|
|
3239
|
+
title: z
|
|
3240
|
+
.string()
|
|
3241
|
+
.min(1)
|
|
3242
|
+
.max(200)
|
|
3243
|
+
.optional()
|
|
3244
|
+
.describe("New product title. 1-200 chars."),
|
|
3245
|
+
status: z
|
|
3246
|
+
.enum(["draft", "publish", "pending", "private"])
|
|
3247
|
+
.optional()
|
|
3248
|
+
.describe("New post status."),
|
|
3249
|
+
content: z.string().optional().describe("New long description."),
|
|
3250
|
+
excerpt: z.string().optional().describe("New short summary."),
|
|
3251
|
+
fulfillment_type: z
|
|
3252
|
+
.enum(["digital", "physical"])
|
|
3253
|
+
.optional()
|
|
3254
|
+
.describe("New fulfillment shape."),
|
|
3255
|
+
price: z
|
|
3256
|
+
.number()
|
|
3257
|
+
.min(0)
|
|
3258
|
+
.optional()
|
|
3259
|
+
.describe("New default-variation item_price (currency units). Non-negative."),
|
|
3260
|
+
compare_price: z
|
|
3261
|
+
.number()
|
|
3262
|
+
.min(0)
|
|
3263
|
+
.optional()
|
|
3264
|
+
.describe("New compare-at price. Must be ≥ `price` when both provided."),
|
|
3265
|
+
sku: z
|
|
3266
|
+
.string()
|
|
3267
|
+
.optional()
|
|
3268
|
+
.describe("New SKU for the default variation. Empty string clears the SKU."),
|
|
3269
|
+
dry_run: DRY_RUN_FIELD,
|
|
3270
|
+
},
|
|
3271
|
+
annotations: { idempotentHint: false },
|
|
3272
|
+
_meta: { idempotent: "conditional" },
|
|
3273
|
+
}, async ({ id, title, status, content, excerpt, fulfillment_type, price, compare_price, sku, dry_run, }) => {
|
|
3274
|
+
const body = {};
|
|
3275
|
+
if (title !== undefined)
|
|
3276
|
+
body.title = title;
|
|
3277
|
+
if (status !== undefined)
|
|
3278
|
+
body.status = status;
|
|
3279
|
+
if (content !== undefined)
|
|
3280
|
+
body.content = content;
|
|
3281
|
+
if (excerpt !== undefined)
|
|
3282
|
+
body.excerpt = excerpt;
|
|
3283
|
+
if (fulfillment_type !== undefined)
|
|
3284
|
+
body.fulfillment_type = fulfillment_type;
|
|
3285
|
+
if (price !== undefined)
|
|
3286
|
+
body.price = price;
|
|
3287
|
+
if (compare_price !== undefined)
|
|
3288
|
+
body.compare_price = compare_price;
|
|
3289
|
+
if (sku !== undefined)
|
|
3290
|
+
body.sku = sku;
|
|
3291
|
+
if (dry_run !== undefined)
|
|
3292
|
+
body.dry_run = dry_run;
|
|
3293
|
+
const result = await wp.requestEnveloped(`/pro/fluentcart/products/${id}/update`, { method: "POST", body });
|
|
3294
|
+
return {
|
|
3295
|
+
content: [
|
|
3296
|
+
{
|
|
3297
|
+
type: "text",
|
|
3298
|
+
text: serializeEnvelope(result, "diviops_fc_product_update"),
|
|
3299
|
+
},
|
|
3300
|
+
],
|
|
3301
|
+
};
|
|
3302
|
+
}, { target: "fluentcart", capabilityKey: "fluentcart_product_update" });
|
|
3303
|
+
// diviops_fc_product_delete — POST /diviops/v1/pro/fluentcart/products/{id}/delete
|
|
3304
|
+
registerProTool("diviops_fc_product_delete", {
|
|
3305
|
+
description: "Trash a FluentCart Pro product (Pro tier; requires FluentCart Pro installed + activated). V2 semantics: trash, NOT hard-delete. Uses `wp_trash_post` (not FluentCart's `ProductResource::delete`, which permanently destroys detail / variation rows) so the trash bin remains recoverable from the FluentCart admin UI. Repeat-safe: trashing an already-trashed product returns `ok:true` with `data.already_trashed: true` (no error). Permanent delete is intentionally NOT in V2 — surfaces in a later vertical with explicit policy. Pending-order protection: a product with at least one on-hold or processing order returns `fluentcart.pending_orders` (HTTP 409) and is not bypassable in V2. Returns the standardized envelope { ok, data?, error: { code, message, hint? } }; apply-mode success payload is { trashed: true, product_id } or { already_trashed: true, product_id }. Error codes: invalid_input (400) when id is not a positive integer; not_found (404) when no product matches; fluentcart.pending_orders (409) when the product has on-hold/processing orders; fluentcart.module_inactive (412); fluentcart.command_failed (500). Idempotency: conditional — repeat trash is a no-op." +
|
|
3306
|
+
DRY_RUN_DESC_SUFFIX,
|
|
3307
|
+
inputSchema: {
|
|
3308
|
+
id: z
|
|
3309
|
+
.number()
|
|
3310
|
+
.int()
|
|
3311
|
+
.positive()
|
|
3312
|
+
.describe("FluentCart product ID (the post ID of the fluent_products CPT entry)."),
|
|
3313
|
+
dry_run: DRY_RUN_FIELD,
|
|
3314
|
+
},
|
|
3315
|
+
annotations: { idempotentHint: false },
|
|
3316
|
+
_meta: { idempotent: "conditional" },
|
|
3317
|
+
}, async ({ id, dry_run }) => {
|
|
3318
|
+
const body = {};
|
|
3319
|
+
if (dry_run !== undefined)
|
|
3320
|
+
body.dry_run = dry_run;
|
|
3321
|
+
const result = await wp.requestEnveloped(`/pro/fluentcart/products/${id}/delete`, { method: "POST", body });
|
|
3322
|
+
return {
|
|
3323
|
+
content: [
|
|
3324
|
+
{
|
|
3325
|
+
type: "text",
|
|
3326
|
+
text: serializeEnvelope(result, "diviops_fc_product_delete"),
|
|
3327
|
+
},
|
|
3328
|
+
],
|
|
3329
|
+
};
|
|
3330
|
+
}, { target: "fluentcart", capabilityKey: "fluentcart_product_delete" });
|
|
3331
|
+
}
|
|
3002
3332
|
// ── Start ────────────────────────────────────────────────────────────
|
|
3003
3333
|
async function main() {
|
|
3004
|
-
// Capability handshake — populate the per-tool gate map (#486)
|
|
3334
|
+
// Capability handshake — populate the per-tool gate map (#486)
|
|
3335
|
+
// and the ADR-003 / ADR-007 Pro-extension surface (target presence,
|
|
3336
|
+
// module activation). On Free-only sites the Pro fields are
|
|
3337
|
+
// normalized to `false` / `{}` by wp-client.
|
|
3005
3338
|
try {
|
|
3006
3339
|
const hs = await wp.handshake(SERVER_VERSION);
|
|
3007
3340
|
handshakeState = {
|
|
3008
3341
|
kind: "ok",
|
|
3009
3342
|
capabilities: hs.capabilities,
|
|
3010
3343
|
pluginVersion: hs.plugin_version,
|
|
3344
|
+
proActive: hs.pro_active === true,
|
|
3345
|
+
availableTargets: hs.available_targets ?? {},
|
|
3346
|
+
activeModules: hs.active_modules ?? {},
|
|
3011
3347
|
};
|
|
3012
3348
|
const diviInfo = hs.divi.active
|
|
3013
3349
|
? `Divi ${hs.divi.version ?? "unknown"}`
|
|
3014
3350
|
: "Divi not active";
|
|
3015
3351
|
const capCount = Object.keys(hs.capabilities).filter((k) => hs.capabilities[k]).length;
|
|
3016
|
-
|
|
3352
|
+
const proInfo = handshakeState.proActive
|
|
3353
|
+
? `Pro active (${hs.pro_version ?? "version unknown"})`
|
|
3354
|
+
: "Pro inactive";
|
|
3355
|
+
console.error(`Handshake OK: plugin ${hs.plugin_version}, ${diviInfo}, ${proInfo}, ${capCount} capabilities`);
|
|
3017
3356
|
if (capCount === 0) {
|
|
3018
3357
|
console.error("Warning: plugin returned an empty capability map. Plugin-touching tools will fail with an upgrade hint. Update diviops-agent to ≥1.2.0.");
|
|
3019
3358
|
}
|
|
@@ -3029,11 +3368,17 @@ async function main() {
|
|
|
3029
3368
|
// failed so plugin-touching tools fall through to their own
|
|
3030
3369
|
// wp.request() calls and surface the real error (401, 5xx, etc.)
|
|
3031
3370
|
// instead of being misreported as missing capabilities.
|
|
3032
|
-
//
|
|
3033
|
-
// cause; the gate must preserve that.
|
|
3371
|
+
// Prior review feedback: the pre-handshake-gate behavior surfaced the
|
|
3372
|
+
// actual cause; the gate must preserve that.
|
|
3034
3373
|
handshakeState = { kind: "failed" };
|
|
3035
3374
|
console.error(`Handshake warning (gate disabled): ${msg}`);
|
|
3036
3375
|
}
|
|
3376
|
+
// Pro coverage-slice registration must run AFTER the handshake so the
|
|
3377
|
+
// gates (`pro_active`, `available_targets`, `active_modules`,
|
|
3378
|
+
// capability map) reflect the connected site's actual state. On Free
|
|
3379
|
+
// sites — or when the handshake failed — registerProTool's internal
|
|
3380
|
+
// gates short-circuit so no Pro tools register.
|
|
3381
|
+
registerProTools();
|
|
3037
3382
|
const transport = new StdioServerTransport();
|
|
3038
3383
|
await server.connect(transport);
|
|
3039
3384
|
console.error("Divi MCP Server running on stdio");
|
package/dist/preset-cli/cli.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `diviops-preset` — standalone preset-emitter CLI
|
|
2
|
+
* `diviops-preset` — standalone preset-emitter CLI.
|
|
3
3
|
*
|
|
4
4
|
* Emits byte-canonical Divi 5.5.x preset JSON, gated by the verified-attrs
|
|
5
|
-
* registry and routed through the existing storage-path contract.
|
|
6
|
-
*
|
|
5
|
+
* registry and routed through the existing storage-path contract. The
|
|
6
|
+
* initial slice shipped the `divi/button` group emitter; subsequent slices
|
|
7
|
+
* added the heading/font, color, and spacing emitters.
|
|
7
8
|
*
|
|
8
9
|
* Usage:
|
|
9
10
|
* diviops-preset button [options] Emit a divi/button group preset
|