elementor-mcp-agent 1.1.0 → 1.3.0
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 +55 -0
- package/dist/server.js +347 -183
- package/dist/server.js.map +1 -1
- package/package.json +13 -7
- package/scripts/fetch-elementor-docs.ts +26 -0
- package/scripts/postinstall.cjs +64 -0
package/dist/server.js
CHANGED
|
@@ -2122,7 +2122,7 @@ var checkElementorVersionsTool = defineTool({
|
|
|
2122
2122
|
});
|
|
2123
2123
|
|
|
2124
2124
|
// src/tools/widgets.ts
|
|
2125
|
-
import { z as
|
|
2125
|
+
import { z as z9 } from "zod";
|
|
2126
2126
|
init_wp_rest();
|
|
2127
2127
|
|
|
2128
2128
|
// src/elementor/widget-ops.ts
|
|
@@ -2213,6 +2213,78 @@ init_backup();
|
|
|
2213
2213
|
init_css_flush();
|
|
2214
2214
|
init_policies();
|
|
2215
2215
|
init_confirmation();
|
|
2216
|
+
|
|
2217
|
+
// src/elementor/verify.ts
|
|
2218
|
+
init_wp_rest();
|
|
2219
|
+
import { z as z8 } from "zod";
|
|
2220
|
+
var VerificationSchema = z8.object({
|
|
2221
|
+
/** Plain-English description of what we re-read and compared. */
|
|
2222
|
+
method: z8.string(),
|
|
2223
|
+
/** Whether a canonical re-read of the page succeeded. */
|
|
2224
|
+
reread_ok: z8.boolean(),
|
|
2225
|
+
/** Op-specific: did the change we requested actually persist? */
|
|
2226
|
+
matches_requested: z8.boolean(),
|
|
2227
|
+
/** Op-specific extra data (e.g. the actual persisted widget settings). */
|
|
2228
|
+
persisted: z8.record(z8.any()).optional(),
|
|
2229
|
+
/** Free-text notes / explanation when matches_requested is false. */
|
|
2230
|
+
notes: z8.string().optional()
|
|
2231
|
+
});
|
|
2232
|
+
async function verifyWrite(args) {
|
|
2233
|
+
try {
|
|
2234
|
+
const page = await wpRequest(
|
|
2235
|
+
`/wp/v2/pages/${args.pageId}?context=edit&_fields=meta`,
|
|
2236
|
+
{ siteId: args.siteId }
|
|
2237
|
+
);
|
|
2238
|
+
const v = page.meta?._elementor_data;
|
|
2239
|
+
const raw = typeof v === "string" ? v : JSON.stringify(v ?? []);
|
|
2240
|
+
const data = parseElementorData(raw);
|
|
2241
|
+
const result = args.predicate(data);
|
|
2242
|
+
return {
|
|
2243
|
+
method: args.description,
|
|
2244
|
+
reread_ok: true,
|
|
2245
|
+
matches_requested: result.ok,
|
|
2246
|
+
persisted: result.persisted,
|
|
2247
|
+
notes: result.notes
|
|
2248
|
+
};
|
|
2249
|
+
} catch (err) {
|
|
2250
|
+
return {
|
|
2251
|
+
method: args.description,
|
|
2252
|
+
reread_ok: false,
|
|
2253
|
+
matches_requested: false,
|
|
2254
|
+
notes: `Canonical re-read failed: ${err.message}`
|
|
2255
|
+
};
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
function deepEqual(a, b) {
|
|
2259
|
+
if (a === b) return true;
|
|
2260
|
+
if (typeof a !== typeof b) return false;
|
|
2261
|
+
if (a === null || b === null) return a === b;
|
|
2262
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
2263
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
2264
|
+
return a.every((x, i) => deepEqual(x, b[i]));
|
|
2265
|
+
}
|
|
2266
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
2267
|
+
const ka = Object.keys(a);
|
|
2268
|
+
const kb = Object.keys(b);
|
|
2269
|
+
if (ka.length !== kb.length) return false;
|
|
2270
|
+
return ka.every(
|
|
2271
|
+
(k) => deepEqual(a[k], b[k])
|
|
2272
|
+
);
|
|
2273
|
+
}
|
|
2274
|
+
return false;
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
// src/tools/widgets.ts
|
|
2278
|
+
var MutationResponseShape = {
|
|
2279
|
+
mode: z9.enum(["dry_run", "applied"]),
|
|
2280
|
+
page_id: z9.number(),
|
|
2281
|
+
confirmation_token: z9.string().optional(),
|
|
2282
|
+
backup_meta_key: z9.string().optional(),
|
|
2283
|
+
css_flush: z9.string().optional(),
|
|
2284
|
+
mutated: z9.boolean().optional(),
|
|
2285
|
+
warnings: z9.array(z9.string()).optional(),
|
|
2286
|
+
verification: VerificationSchema.optional()
|
|
2287
|
+
};
|
|
2216
2288
|
async function fetchData(siteId, pageId) {
|
|
2217
2289
|
const page = await wpRequest(
|
|
2218
2290
|
`/wp/v2/pages/${pageId}?context=edit&_fields=meta`,
|
|
@@ -2234,20 +2306,20 @@ async function writeData(siteId, pageId, data) {
|
|
|
2234
2306
|
body: { meta: { _elementor_data: ser } }
|
|
2235
2307
|
});
|
|
2236
2308
|
const flush = await flushCSS(siteId, pageId);
|
|
2237
|
-
return { method: flush.method };
|
|
2309
|
+
return { method: flush.method, serialized: ser };
|
|
2238
2310
|
}
|
|
2239
2311
|
var readWidgetTool = defineTool({
|
|
2240
2312
|
name: "read_widget",
|
|
2241
2313
|
description: "Fetch a single widget's full settings by id. Use list_widgets_in_page to find the id first.",
|
|
2242
|
-
inputSchema:
|
|
2243
|
-
site_id:
|
|
2244
|
-
page_id:
|
|
2245
|
-
widget_id:
|
|
2314
|
+
inputSchema: z9.object({
|
|
2315
|
+
site_id: z9.string().optional(),
|
|
2316
|
+
page_id: z9.number().int().positive(),
|
|
2317
|
+
widget_id: z9.string().min(1)
|
|
2246
2318
|
}),
|
|
2247
|
-
outputSchema:
|
|
2248
|
-
widget_id:
|
|
2249
|
-
widget_type:
|
|
2250
|
-
settings:
|
|
2319
|
+
outputSchema: z9.object({
|
|
2320
|
+
widget_id: z9.string(),
|
|
2321
|
+
widget_type: z9.string().optional(),
|
|
2322
|
+
settings: z9.record(z9.any())
|
|
2251
2323
|
}),
|
|
2252
2324
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
2253
2325
|
async handler(input) {
|
|
@@ -2259,22 +2331,18 @@ var readWidgetTool = defineTool({
|
|
|
2259
2331
|
});
|
|
2260
2332
|
var updateWidgetSettingsTool = defineTool({
|
|
2261
2333
|
name: "update_widget_settings",
|
|
2262
|
-
description: "Shallow-merge a partial settings object into one widget. Backs up the page first
|
|
2263
|
-
inputSchema:
|
|
2264
|
-
site_id:
|
|
2265
|
-
page_id:
|
|
2266
|
-
widget_id:
|
|
2267
|
-
settings_patch:
|
|
2268
|
-
confirmation:
|
|
2269
|
-
}),
|
|
2270
|
-
outputSchema:
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
keys_changed: z8.array(z8.string()),
|
|
2275
|
-
confirmation_token: z8.string().optional(),
|
|
2276
|
-
backup_meta_key: z8.string().optional(),
|
|
2277
|
-
css_flush: z8.string().optional()
|
|
2334
|
+
description: "Shallow-merge a partial settings object into one widget. Backs up the page first, validates the result, auto-flushes CSS, then re-reads the page and verifies the patch persisted (matches_requested in the response). Two-call confirmation.",
|
|
2335
|
+
inputSchema: z9.object({
|
|
2336
|
+
site_id: z9.string().optional(),
|
|
2337
|
+
page_id: z9.number().int().positive(),
|
|
2338
|
+
widget_id: z9.string().min(1),
|
|
2339
|
+
settings_patch: z9.record(z9.any()),
|
|
2340
|
+
confirmation: z9.string().optional()
|
|
2341
|
+
}),
|
|
2342
|
+
outputSchema: z9.object({
|
|
2343
|
+
...MutationResponseShape,
|
|
2344
|
+
widget_id: z9.string(),
|
|
2345
|
+
keys_changed: z9.array(z9.string())
|
|
2278
2346
|
}),
|
|
2279
2347
|
annotations: { destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
2280
2348
|
async handler(input) {
|
|
@@ -2290,38 +2358,56 @@ var updateWidgetSettingsTool = defineTool({
|
|
|
2290
2358
|
}
|
|
2291
2359
|
const conf = consumeConfirmation(input.confirmation, "update_widget_settings");
|
|
2292
2360
|
if (!conf) throw new Error("Invalid or expired confirmation token");
|
|
2293
|
-
const { data } = await fetchData(input.site_id, input.page_id);
|
|
2361
|
+
const { raw: rawBefore, data } = await fetchData(input.site_id, input.page_id);
|
|
2294
2362
|
if (!updateWidgetSettings(data, input.widget_id, input.settings_patch)) {
|
|
2295
2363
|
throw new Error(`Widget ${input.widget_id} not found`);
|
|
2296
2364
|
}
|
|
2297
2365
|
const backup = await fullBackup(input.site_id, input.page_id);
|
|
2298
2366
|
const w = await writeData(input.site_id, input.page_id, data);
|
|
2367
|
+
const verification = await verifyWrite({
|
|
2368
|
+
siteId: input.site_id,
|
|
2369
|
+
pageId: input.page_id,
|
|
2370
|
+
description: `Re-read /wp/v2/pages/${input.page_id} and check widget ${input.widget_id} settings include the requested patch`,
|
|
2371
|
+
predicate: (canonical) => {
|
|
2372
|
+
const widget = findElementById(canonical, input.widget_id);
|
|
2373
|
+
if (!widget) return { ok: false, notes: "Widget no longer present after write" };
|
|
2374
|
+
const persisted = widget.settings;
|
|
2375
|
+
const mismatches = [];
|
|
2376
|
+
for (const [k, want] of Object.entries(input.settings_patch)) {
|
|
2377
|
+
if (!deepEqual(persisted[k], want)) mismatches.push(k);
|
|
2378
|
+
}
|
|
2379
|
+
return {
|
|
2380
|
+
ok: mismatches.length === 0,
|
|
2381
|
+
persisted,
|
|
2382
|
+
notes: mismatches.length === 0 ? void 0 : `Persisted state diverges from requested patch on key(s): ${mismatches.join(", ")}`
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
});
|
|
2299
2386
|
return {
|
|
2300
2387
|
mode: "applied",
|
|
2301
2388
|
page_id: input.page_id,
|
|
2302
2389
|
widget_id: input.widget_id,
|
|
2303
2390
|
keys_changed: Object.keys(input.settings_patch),
|
|
2304
2391
|
backup_meta_key: backup.meta_key,
|
|
2305
|
-
css_flush: w.method
|
|
2392
|
+
css_flush: w.method,
|
|
2393
|
+
mutated: rawBefore !== w.serialized,
|
|
2394
|
+
warnings: [],
|
|
2395
|
+
verification
|
|
2306
2396
|
};
|
|
2307
2397
|
}
|
|
2308
2398
|
});
|
|
2309
2399
|
var deleteWidgetTool = defineTool({
|
|
2310
2400
|
name: "delete_widget",
|
|
2311
|
-
description: "Remove a widget from a page by id. Two-call confirmation. Backs up before deleting.",
|
|
2312
|
-
inputSchema:
|
|
2313
|
-
site_id:
|
|
2314
|
-
page_id:
|
|
2315
|
-
widget_id:
|
|
2316
|
-
confirmation:
|
|
2317
|
-
}),
|
|
2318
|
-
outputSchema:
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
widget_id: z8.string(),
|
|
2322
|
-
confirmation_token: z8.string().optional(),
|
|
2323
|
-
backup_meta_key: z8.string().optional(),
|
|
2324
|
-
css_flush: z8.string().optional()
|
|
2401
|
+
description: "Remove a widget from a page by id. Two-call confirmation. Backs up before deleting; re-reads to confirm the widget is gone.",
|
|
2402
|
+
inputSchema: z9.object({
|
|
2403
|
+
site_id: z9.string().optional(),
|
|
2404
|
+
page_id: z9.number().int().positive(),
|
|
2405
|
+
widget_id: z9.string().min(1),
|
|
2406
|
+
confirmation: z9.string().optional()
|
|
2407
|
+
}),
|
|
2408
|
+
outputSchema: z9.object({
|
|
2409
|
+
...MutationResponseShape,
|
|
2410
|
+
widget_id: z9.string()
|
|
2325
2411
|
}),
|
|
2326
2412
|
annotations: { destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
2327
2413
|
async handler(input) {
|
|
@@ -2331,36 +2417,47 @@ var deleteWidgetTool = defineTool({
|
|
|
2331
2417
|
}
|
|
2332
2418
|
const conf = consumeConfirmation(input.confirmation, "delete_widget");
|
|
2333
2419
|
if (!conf) throw new Error("Invalid or expired confirmation token");
|
|
2334
|
-
const { data } = await fetchData(input.site_id, input.page_id);
|
|
2420
|
+
const { raw: rawBefore, data } = await fetchData(input.site_id, input.page_id);
|
|
2335
2421
|
if (!deleteWidget(data, input.widget_id)) throw new Error(`Widget ${input.widget_id} not found`);
|
|
2336
2422
|
const backup = await fullBackup(input.site_id, input.page_id);
|
|
2337
2423
|
const w = await writeData(input.site_id, input.page_id, data);
|
|
2424
|
+
const verification = await verifyWrite({
|
|
2425
|
+
siteId: input.site_id,
|
|
2426
|
+
pageId: input.page_id,
|
|
2427
|
+
description: `Re-read /wp/v2/pages/${input.page_id} and assert widget ${input.widget_id} is gone`,
|
|
2428
|
+
predicate: (canonical) => {
|
|
2429
|
+
const found = findElementById(canonical, input.widget_id);
|
|
2430
|
+
return {
|
|
2431
|
+
ok: found === null,
|
|
2432
|
+
notes: found ? "Widget still present in canonical re-read \u2014 delete did not persist" : void 0
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
});
|
|
2338
2436
|
return {
|
|
2339
2437
|
mode: "applied",
|
|
2340
2438
|
page_id: input.page_id,
|
|
2341
2439
|
widget_id: input.widget_id,
|
|
2342
2440
|
backup_meta_key: backup.meta_key,
|
|
2343
|
-
css_flush: w.method
|
|
2441
|
+
css_flush: w.method,
|
|
2442
|
+
mutated: rawBefore !== w.serialized,
|
|
2443
|
+
warnings: [],
|
|
2444
|
+
verification
|
|
2344
2445
|
};
|
|
2345
2446
|
}
|
|
2346
2447
|
});
|
|
2347
2448
|
var duplicateWidgetTool = defineTool({
|
|
2348
2449
|
name: "duplicate_widget",
|
|
2349
|
-
description: "Duplicate a widget in place (right after the original). The clone gets a new id. Two-call confirmation.",
|
|
2350
|
-
inputSchema:
|
|
2351
|
-
site_id:
|
|
2352
|
-
page_id:
|
|
2353
|
-
widget_id:
|
|
2354
|
-
confirmation:
|
|
2355
|
-
}),
|
|
2356
|
-
outputSchema:
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
new_widget_id: z8.string().optional(),
|
|
2361
|
-
confirmation_token: z8.string().optional(),
|
|
2362
|
-
backup_meta_key: z8.string().optional(),
|
|
2363
|
-
css_flush: z8.string().optional()
|
|
2450
|
+
description: "Duplicate a widget in place (right after the original). The clone gets a new id. Re-reads to confirm the clone persisted. Two-call confirmation.",
|
|
2451
|
+
inputSchema: z9.object({
|
|
2452
|
+
site_id: z9.string().optional(),
|
|
2453
|
+
page_id: z9.number().int().positive(),
|
|
2454
|
+
widget_id: z9.string().min(1),
|
|
2455
|
+
confirmation: z9.string().optional()
|
|
2456
|
+
}),
|
|
2457
|
+
outputSchema: z9.object({
|
|
2458
|
+
...MutationResponseShape,
|
|
2459
|
+
source_widget_id: z9.string(),
|
|
2460
|
+
new_widget_id: z9.string().optional()
|
|
2364
2461
|
}),
|
|
2365
2462
|
annotations: { destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
2366
2463
|
async handler(input) {
|
|
@@ -2370,40 +2467,53 @@ var duplicateWidgetTool = defineTool({
|
|
|
2370
2467
|
}
|
|
2371
2468
|
const conf = consumeConfirmation(input.confirmation, "duplicate_widget");
|
|
2372
2469
|
if (!conf) throw new Error("Invalid or expired confirmation token");
|
|
2373
|
-
const { data } = await fetchData(input.site_id, input.page_id);
|
|
2470
|
+
const { raw: rawBefore, data } = await fetchData(input.site_id, input.page_id);
|
|
2374
2471
|
const r = duplicateWidget(data, input.widget_id);
|
|
2375
|
-
if (!r.ok) throw new Error(`Widget ${input.widget_id} not found`);
|
|
2472
|
+
if (!r.ok || !r.new_widget_id) throw new Error(`Widget ${input.widget_id} not found`);
|
|
2473
|
+
const newId = r.new_widget_id;
|
|
2376
2474
|
const backup = await fullBackup(input.site_id, input.page_id);
|
|
2377
2475
|
const w = await writeData(input.site_id, input.page_id, data);
|
|
2476
|
+
const verification = await verifyWrite({
|
|
2477
|
+
siteId: input.site_id,
|
|
2478
|
+
pageId: input.page_id,
|
|
2479
|
+
description: `Re-read /wp/v2/pages/${input.page_id} and assert the clone (${newId}) exists alongside the source`,
|
|
2480
|
+
predicate: (canonical) => {
|
|
2481
|
+
const source = findElementById(canonical, input.widget_id);
|
|
2482
|
+
const clone = findElementById(canonical, newId);
|
|
2483
|
+
return {
|
|
2484
|
+
ok: source !== null && clone !== null,
|
|
2485
|
+
notes: !clone ? "Clone not present in canonical re-read" : !source ? "Original is missing after duplicate" : void 0
|
|
2486
|
+
};
|
|
2487
|
+
}
|
|
2488
|
+
});
|
|
2378
2489
|
return {
|
|
2379
2490
|
mode: "applied",
|
|
2380
2491
|
page_id: input.page_id,
|
|
2381
2492
|
source_widget_id: input.widget_id,
|
|
2382
|
-
new_widget_id:
|
|
2493
|
+
new_widget_id: newId,
|
|
2383
2494
|
backup_meta_key: backup.meta_key,
|
|
2384
|
-
css_flush: w.method
|
|
2495
|
+
css_flush: w.method,
|
|
2496
|
+
mutated: rawBefore !== w.serialized,
|
|
2497
|
+
warnings: [],
|
|
2498
|
+
verification
|
|
2385
2499
|
};
|
|
2386
2500
|
}
|
|
2387
2501
|
});
|
|
2388
2502
|
var swapWidgetTypeTool = defineTool({
|
|
2389
2503
|
name: "swap_widget_type",
|
|
2390
|
-
description: "Replace a widget's type (e.g., heading \u2192 button) while preserving its position. Provide full new_settings \u2014 the old settings are NOT carried over (different widget types have incompatible schemas). Two-call confirmation.",
|
|
2391
|
-
inputSchema:
|
|
2392
|
-
site_id:
|
|
2393
|
-
page_id:
|
|
2394
|
-
widget_id:
|
|
2395
|
-
new_widget_type:
|
|
2396
|
-
new_settings:
|
|
2397
|
-
confirmation:
|
|
2398
|
-
}),
|
|
2399
|
-
outputSchema:
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
new_widget_type: z8.string(),
|
|
2404
|
-
confirmation_token: z8.string().optional(),
|
|
2405
|
-
backup_meta_key: z8.string().optional(),
|
|
2406
|
-
css_flush: z8.string().optional()
|
|
2504
|
+
description: "Replace a widget's type (e.g., heading \u2192 button) while preserving its id and position. Provide full new_settings \u2014 the old settings are NOT carried over (different widget types have incompatible schemas). Re-reads to confirm. Two-call confirmation.",
|
|
2505
|
+
inputSchema: z9.object({
|
|
2506
|
+
site_id: z9.string().optional(),
|
|
2507
|
+
page_id: z9.number().int().positive(),
|
|
2508
|
+
widget_id: z9.string().min(1),
|
|
2509
|
+
new_widget_type: z9.string().min(1),
|
|
2510
|
+
new_settings: z9.record(z9.any()).default({}),
|
|
2511
|
+
confirmation: z9.string().optional()
|
|
2512
|
+
}),
|
|
2513
|
+
outputSchema: z9.object({
|
|
2514
|
+
...MutationResponseShape,
|
|
2515
|
+
widget_id: z9.string(),
|
|
2516
|
+
new_widget_type: z9.string()
|
|
2407
2517
|
}),
|
|
2408
2518
|
annotations: { destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
2409
2519
|
async handler(input) {
|
|
@@ -2413,42 +2523,55 @@ var swapWidgetTypeTool = defineTool({
|
|
|
2413
2523
|
}
|
|
2414
2524
|
const conf = consumeConfirmation(input.confirmation, "swap_widget_type");
|
|
2415
2525
|
if (!conf) throw new Error("Invalid or expired confirmation token");
|
|
2416
|
-
const { data } = await fetchData(input.site_id, input.page_id);
|
|
2526
|
+
const { raw: rawBefore, data } = await fetchData(input.site_id, input.page_id);
|
|
2417
2527
|
if (!swapWidgetType(data, input.widget_id, input.new_widget_type, input.new_settings)) {
|
|
2418
2528
|
throw new Error(`Widget ${input.widget_id} not found`);
|
|
2419
2529
|
}
|
|
2420
2530
|
const backup = await fullBackup(input.site_id, input.page_id);
|
|
2421
2531
|
const w = await writeData(input.site_id, input.page_id, data);
|
|
2532
|
+
const verification = await verifyWrite({
|
|
2533
|
+
siteId: input.site_id,
|
|
2534
|
+
pageId: input.page_id,
|
|
2535
|
+
description: `Re-read /wp/v2/pages/${input.page_id} and assert widget ${input.widget_id} now has widgetType="${input.new_widget_type}"`,
|
|
2536
|
+
predicate: (canonical) => {
|
|
2537
|
+
const widget = findElementById(canonical, input.widget_id);
|
|
2538
|
+
if (!widget) return { ok: false, notes: "Widget missing after swap" };
|
|
2539
|
+
return {
|
|
2540
|
+
ok: widget.widgetType === input.new_widget_type,
|
|
2541
|
+
persisted: { widgetType: widget.widgetType, settings: widget.settings },
|
|
2542
|
+
notes: widget.widgetType === input.new_widget_type ? void 0 : `Expected widgetType="${input.new_widget_type}", canonical state has "${widget.widgetType}"`
|
|
2543
|
+
};
|
|
2544
|
+
}
|
|
2545
|
+
});
|
|
2422
2546
|
return {
|
|
2423
2547
|
mode: "applied",
|
|
2424
2548
|
page_id: input.page_id,
|
|
2425
2549
|
widget_id: input.widget_id,
|
|
2426
2550
|
new_widget_type: input.new_widget_type,
|
|
2427
2551
|
backup_meta_key: backup.meta_key,
|
|
2428
|
-
css_flush: w.method
|
|
2552
|
+
css_flush: w.method,
|
|
2553
|
+
mutated: rawBefore !== w.serialized,
|
|
2554
|
+
warnings: [],
|
|
2555
|
+
verification
|
|
2429
2556
|
};
|
|
2430
2557
|
}
|
|
2431
2558
|
});
|
|
2432
2559
|
var addWidgetTool = defineTool({
|
|
2433
2560
|
name: "add_widget",
|
|
2434
|
-
description: "Append a new widget to a parent container (section, column, or container) on a page. Two-call confirmation.",
|
|
2435
|
-
inputSchema:
|
|
2436
|
-
site_id:
|
|
2437
|
-
page_id:
|
|
2438
|
-
parent_id:
|
|
2439
|
-
widget_type:
|
|
2440
|
-
settings:
|
|
2441
|
-
confirmation:
|
|
2442
|
-
}),
|
|
2443
|
-
outputSchema:
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
new_widget_id: z8.string().optional(),
|
|
2449
|
-
confirmation_token: z8.string().optional(),
|
|
2450
|
-
backup_meta_key: z8.string().optional(),
|
|
2451
|
-
css_flush: z8.string().optional()
|
|
2561
|
+
description: "Append a new widget to a parent container (section, column, or container) on a page. Re-reads to confirm the new widget exists under the parent. Two-call confirmation.",
|
|
2562
|
+
inputSchema: z9.object({
|
|
2563
|
+
site_id: z9.string().optional(),
|
|
2564
|
+
page_id: z9.number().int().positive(),
|
|
2565
|
+
parent_id: z9.string().min(1).describe("Id of the section/column/container that will receive the widget."),
|
|
2566
|
+
widget_type: z9.string().min(1).describe("e.g., 'heading', 'text-editor', 'button', 'image'."),
|
|
2567
|
+
settings: z9.record(z9.any()).default({}),
|
|
2568
|
+
confirmation: z9.string().optional()
|
|
2569
|
+
}),
|
|
2570
|
+
outputSchema: z9.object({
|
|
2571
|
+
...MutationResponseShape,
|
|
2572
|
+
parent_id: z9.string(),
|
|
2573
|
+
widget_type: z9.string(),
|
|
2574
|
+
new_widget_id: z9.string().optional()
|
|
2452
2575
|
}),
|
|
2453
2576
|
annotations: { destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
2454
2577
|
async handler(input) {
|
|
@@ -2458,41 +2581,56 @@ var addWidgetTool = defineTool({
|
|
|
2458
2581
|
}
|
|
2459
2582
|
const conf = consumeConfirmation(input.confirmation, "add_widget");
|
|
2460
2583
|
if (!conf) throw new Error("Invalid or expired confirmation token");
|
|
2461
|
-
const { data } = await fetchData(input.site_id, input.page_id);
|
|
2584
|
+
const { raw: rawBefore, data } = await fetchData(input.site_id, input.page_id);
|
|
2462
2585
|
const r = addWidget(data, input.parent_id, input.widget_type, input.settings);
|
|
2463
|
-
if (!r.ok) throw new Error(`Parent ${input.parent_id} not found`);
|
|
2586
|
+
if (!r.ok || !r.new_widget_id) throw new Error(`Parent ${input.parent_id} not found`);
|
|
2587
|
+
const newId = r.new_widget_id;
|
|
2464
2588
|
const backup = await fullBackup(input.site_id, input.page_id);
|
|
2465
2589
|
const w = await writeData(input.site_id, input.page_id, data);
|
|
2590
|
+
const verification = await verifyWrite({
|
|
2591
|
+
siteId: input.site_id,
|
|
2592
|
+
pageId: input.page_id,
|
|
2593
|
+
description: `Re-read /wp/v2/pages/${input.page_id} and assert new widget ${newId} exists under parent ${input.parent_id} with widgetType="${input.widget_type}"`,
|
|
2594
|
+
predicate: (canonical) => {
|
|
2595
|
+
const widget = findElementById(canonical, newId);
|
|
2596
|
+
if (!widget) return { ok: false, notes: "New widget not found after add" };
|
|
2597
|
+
const parent = findElementById(canonical, input.parent_id);
|
|
2598
|
+
const isUnderParent = parent?.elements?.some((e) => e.id === newId) === true;
|
|
2599
|
+
return {
|
|
2600
|
+
ok: widget.widgetType === input.widget_type && isUnderParent,
|
|
2601
|
+
notes: !isUnderParent ? `Widget exists but is not under expected parent ${input.parent_id}` : widget.widgetType !== input.widget_type ? `Widget exists with wrong widgetType "${widget.widgetType}"` : void 0
|
|
2602
|
+
};
|
|
2603
|
+
}
|
|
2604
|
+
});
|
|
2466
2605
|
return {
|
|
2467
2606
|
mode: "applied",
|
|
2468
2607
|
page_id: input.page_id,
|
|
2469
2608
|
parent_id: input.parent_id,
|
|
2470
2609
|
widget_type: input.widget_type,
|
|
2471
|
-
new_widget_id:
|
|
2610
|
+
new_widget_id: newId,
|
|
2472
2611
|
backup_meta_key: backup.meta_key,
|
|
2473
|
-
css_flush: w.method
|
|
2612
|
+
css_flush: w.method,
|
|
2613
|
+
mutated: rawBefore !== w.serialized,
|
|
2614
|
+
warnings: [],
|
|
2615
|
+
verification
|
|
2474
2616
|
};
|
|
2475
2617
|
}
|
|
2476
2618
|
});
|
|
2477
2619
|
var moveWidgetTool = defineTool({
|
|
2478
2620
|
name: "move_widget",
|
|
2479
|
-
description: "Move a widget to a different parent (or different position in the same parent). Two-call confirmation.",
|
|
2480
|
-
inputSchema:
|
|
2481
|
-
site_id:
|
|
2482
|
-
page_id:
|
|
2483
|
-
widget_id:
|
|
2484
|
-
new_parent_id:
|
|
2485
|
-
position:
|
|
2486
|
-
confirmation:
|
|
2487
|
-
}),
|
|
2488
|
-
outputSchema:
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
new_parent_id: z8.string(),
|
|
2493
|
-
confirmation_token: z8.string().optional(),
|
|
2494
|
-
backup_meta_key: z8.string().optional(),
|
|
2495
|
-
css_flush: z8.string().optional()
|
|
2621
|
+
description: "Move a widget to a different parent (or different position in the same parent). Re-reads to confirm new parent. Two-call confirmation.",
|
|
2622
|
+
inputSchema: z9.object({
|
|
2623
|
+
site_id: z9.string().optional(),
|
|
2624
|
+
page_id: z9.number().int().positive(),
|
|
2625
|
+
widget_id: z9.string().min(1),
|
|
2626
|
+
new_parent_id: z9.string().min(1),
|
|
2627
|
+
position: z9.number().int().default(-1).describe("0-based position in the new parent. -1 = append."),
|
|
2628
|
+
confirmation: z9.string().optional()
|
|
2629
|
+
}),
|
|
2630
|
+
outputSchema: z9.object({
|
|
2631
|
+
...MutationResponseShape,
|
|
2632
|
+
widget_id: z9.string(),
|
|
2633
|
+
new_parent_id: z9.string()
|
|
2496
2634
|
}),
|
|
2497
2635
|
annotations: { destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
2498
2636
|
async handler(input) {
|
|
@@ -2502,25 +2640,41 @@ var moveWidgetTool = defineTool({
|
|
|
2502
2640
|
}
|
|
2503
2641
|
const conf = consumeConfirmation(input.confirmation, "move_widget");
|
|
2504
2642
|
if (!conf) throw new Error("Invalid or expired confirmation token");
|
|
2505
|
-
const { data } = await fetchData(input.site_id, input.page_id);
|
|
2643
|
+
const { raw: rawBefore, data } = await fetchData(input.site_id, input.page_id);
|
|
2506
2644
|
if (!moveWidget(data, input.widget_id, input.new_parent_id, input.position)) {
|
|
2507
2645
|
throw new Error(`Widget or parent not found`);
|
|
2508
2646
|
}
|
|
2509
2647
|
const backup = await fullBackup(input.site_id, input.page_id);
|
|
2510
2648
|
const w = await writeData(input.site_id, input.page_id, data);
|
|
2649
|
+
const verification = await verifyWrite({
|
|
2650
|
+
siteId: input.site_id,
|
|
2651
|
+
pageId: input.page_id,
|
|
2652
|
+
description: `Re-read /wp/v2/pages/${input.page_id} and assert widget ${input.widget_id} is now a direct child of ${input.new_parent_id}`,
|
|
2653
|
+
predicate: (canonical) => {
|
|
2654
|
+
const parent = findElementById(canonical, input.new_parent_id);
|
|
2655
|
+
const isUnderNewParent = parent?.elements?.some((e) => e.id === input.widget_id) === true;
|
|
2656
|
+
return {
|
|
2657
|
+
ok: isUnderNewParent,
|
|
2658
|
+
notes: isUnderNewParent ? void 0 : `Widget ${input.widget_id} not found under ${input.new_parent_id} after move`
|
|
2659
|
+
};
|
|
2660
|
+
}
|
|
2661
|
+
});
|
|
2511
2662
|
return {
|
|
2512
2663
|
mode: "applied",
|
|
2513
2664
|
page_id: input.page_id,
|
|
2514
2665
|
widget_id: input.widget_id,
|
|
2515
2666
|
new_parent_id: input.new_parent_id,
|
|
2516
2667
|
backup_meta_key: backup.meta_key,
|
|
2517
|
-
css_flush: w.method
|
|
2668
|
+
css_flush: w.method,
|
|
2669
|
+
mutated: rawBefore !== w.serialized,
|
|
2670
|
+
warnings: [],
|
|
2671
|
+
verification
|
|
2518
2672
|
};
|
|
2519
2673
|
}
|
|
2520
2674
|
});
|
|
2521
2675
|
|
|
2522
2676
|
// src/tools/bulk.ts
|
|
2523
|
-
import { z as
|
|
2677
|
+
import { z as z10 } from "zod";
|
|
2524
2678
|
init_wp_rest();
|
|
2525
2679
|
init_config();
|
|
2526
2680
|
init_backup();
|
|
@@ -2553,33 +2707,33 @@ async function listElementorPageIds(siteId) {
|
|
|
2553
2707
|
var bulkFindReplaceSiteTool = defineTool({
|
|
2554
2708
|
name: "bulk_find_replace_site",
|
|
2555
2709
|
description: "Find/replace plain text in every Elementor page on a single site. TWO-CALL FLOW: dry-run returns per-page match_count + total + confirmation_token. Apply iterates each page (auto-backup + validate + flush per page). Slower than wp_search_replace but works without SSH and gives per-page granularity.",
|
|
2556
|
-
inputSchema:
|
|
2557
|
-
site_id:
|
|
2558
|
-
find:
|
|
2559
|
-
replace:
|
|
2560
|
-
widget_type:
|
|
2561
|
-
case_sensitive:
|
|
2562
|
-
confirmation:
|
|
2563
|
-
}),
|
|
2564
|
-
outputSchema:
|
|
2565
|
-
mode:
|
|
2566
|
-
site_id:
|
|
2567
|
-
pages_scanned:
|
|
2568
|
-
total_match_count:
|
|
2569
|
-
pages_with_matches:
|
|
2570
|
-
page_id:
|
|
2571
|
-
title:
|
|
2572
|
-
match_count:
|
|
2710
|
+
inputSchema: z10.object({
|
|
2711
|
+
site_id: z10.string().optional(),
|
|
2712
|
+
find: z10.string().min(1),
|
|
2713
|
+
replace: z10.string(),
|
|
2714
|
+
widget_type: z10.string().optional(),
|
|
2715
|
+
case_sensitive: z10.boolean().default(false),
|
|
2716
|
+
confirmation: z10.string().optional()
|
|
2717
|
+
}),
|
|
2718
|
+
outputSchema: z10.object({
|
|
2719
|
+
mode: z10.enum(["dry_run", "applied"]),
|
|
2720
|
+
site_id: z10.string(),
|
|
2721
|
+
pages_scanned: z10.number(),
|
|
2722
|
+
total_match_count: z10.number(),
|
|
2723
|
+
pages_with_matches: z10.array(z10.object({
|
|
2724
|
+
page_id: z10.number(),
|
|
2725
|
+
title: z10.string(),
|
|
2726
|
+
match_count: z10.number()
|
|
2573
2727
|
})),
|
|
2574
|
-
pages_applied:
|
|
2575
|
-
page_id:
|
|
2576
|
-
backup_meta_key:
|
|
2577
|
-
css_flush:
|
|
2578
|
-
mode:
|
|
2579
|
-
error:
|
|
2728
|
+
pages_applied: z10.array(z10.object({
|
|
2729
|
+
page_id: z10.number(),
|
|
2730
|
+
backup_meta_key: z10.string().optional(),
|
|
2731
|
+
css_flush: z10.string().optional(),
|
|
2732
|
+
mode: z10.enum(["applied", "rolled_back", "skipped"]),
|
|
2733
|
+
error: z10.string().optional()
|
|
2580
2734
|
})).optional(),
|
|
2581
|
-
confirmation_token:
|
|
2582
|
-
expires_in_seconds:
|
|
2735
|
+
confirmation_token: z10.string().optional(),
|
|
2736
|
+
expires_in_seconds: z10.number().optional()
|
|
2583
2737
|
}),
|
|
2584
2738
|
annotations: { destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
2585
2739
|
async handler(input) {
|
|
@@ -2662,27 +2816,27 @@ var bulkFindReplaceSiteTool = defineTool({
|
|
|
2662
2816
|
var fleetFindReplaceTool = defineTool({
|
|
2663
2817
|
name: "fleet_find_replace",
|
|
2664
2818
|
description: "Find/replace plain text across every Elementor page of every site in the pool. Same flow as bulk_find_replace_site but iterates across sites. Returns per-site + grand-total summary. Dry-run first; second call applies. Use sparingly \u2014 this is the nuclear option.",
|
|
2665
|
-
inputSchema:
|
|
2666
|
-
find:
|
|
2667
|
-
replace:
|
|
2668
|
-
site_ids:
|
|
2669
|
-
widget_type:
|
|
2670
|
-
case_sensitive:
|
|
2671
|
-
confirmation:
|
|
2672
|
-
}),
|
|
2673
|
-
outputSchema:
|
|
2674
|
-
mode:
|
|
2675
|
-
sites_scanned:
|
|
2676
|
-
total_match_count:
|
|
2677
|
-
by_site:
|
|
2678
|
-
site_id:
|
|
2679
|
-
url:
|
|
2680
|
-
pages_scanned:
|
|
2681
|
-
matches:
|
|
2682
|
-
error:
|
|
2819
|
+
inputSchema: z10.object({
|
|
2820
|
+
find: z10.string().min(1),
|
|
2821
|
+
replace: z10.string(),
|
|
2822
|
+
site_ids: z10.array(z10.string()).optional().describe("Subset of sites to hit. Defaults to all."),
|
|
2823
|
+
widget_type: z10.string().optional(),
|
|
2824
|
+
case_sensitive: z10.boolean().default(false),
|
|
2825
|
+
confirmation: z10.string().optional()
|
|
2826
|
+
}),
|
|
2827
|
+
outputSchema: z10.object({
|
|
2828
|
+
mode: z10.enum(["dry_run", "applied"]),
|
|
2829
|
+
sites_scanned: z10.number(),
|
|
2830
|
+
total_match_count: z10.number(),
|
|
2831
|
+
by_site: z10.array(z10.object({
|
|
2832
|
+
site_id: z10.string(),
|
|
2833
|
+
url: z10.string(),
|
|
2834
|
+
pages_scanned: z10.number(),
|
|
2835
|
+
matches: z10.number(),
|
|
2836
|
+
error: z10.string().optional()
|
|
2683
2837
|
})),
|
|
2684
|
-
confirmation_token:
|
|
2685
|
-
expires_in_seconds:
|
|
2838
|
+
confirmation_token: z10.string().optional(),
|
|
2839
|
+
expires_in_seconds: z10.number().optional()
|
|
2686
2840
|
}),
|
|
2687
2841
|
annotations: { destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
2688
2842
|
async handler(input) {
|
|
@@ -2770,20 +2924,20 @@ var fleetFindReplaceTool = defineTool({
|
|
|
2770
2924
|
var restoreFromFileTool = defineTool({
|
|
2771
2925
|
name: "restore_from_file",
|
|
2772
2926
|
description: "Restore a page from a JSON backup file (created by ANY earlier op with backup_to_file=true or by direct fullBackup with to_file). Requires the file_path returned by that backup. Two-call confirmation.",
|
|
2773
|
-
inputSchema:
|
|
2774
|
-
site_id:
|
|
2775
|
-
page_id:
|
|
2776
|
-
file_path:
|
|
2777
|
-
confirmation:
|
|
2778
|
-
}),
|
|
2779
|
-
outputSchema:
|
|
2780
|
-
mode:
|
|
2781
|
-
page_id:
|
|
2782
|
-
file_path:
|
|
2783
|
-
method:
|
|
2784
|
-
pre_restore_backup_meta_key:
|
|
2785
|
-
css_flush:
|
|
2786
|
-
confirmation_token:
|
|
2927
|
+
inputSchema: z10.object({
|
|
2928
|
+
site_id: z10.string().optional(),
|
|
2929
|
+
page_id: z10.number().int().positive(),
|
|
2930
|
+
file_path: z10.string().min(1),
|
|
2931
|
+
confirmation: z10.string().optional()
|
|
2932
|
+
}),
|
|
2933
|
+
outputSchema: z10.object({
|
|
2934
|
+
mode: z10.enum(["dry_run", "restored"]),
|
|
2935
|
+
page_id: z10.number(),
|
|
2936
|
+
file_path: z10.string(),
|
|
2937
|
+
method: z10.enum(["wp-cli", "rest"]).optional(),
|
|
2938
|
+
pre_restore_backup_meta_key: z10.string().optional(),
|
|
2939
|
+
css_flush: z10.string().optional(),
|
|
2940
|
+
confirmation_token: z10.string().optional()
|
|
2787
2941
|
}),
|
|
2788
2942
|
annotations: { destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
2789
2943
|
async handler(input) {
|
|
@@ -2955,6 +3109,16 @@ ${msg}` }],
|
|
|
2955
3109
|
const transport = new StdioServerTransport();
|
|
2956
3110
|
await server.connect(transport);
|
|
2957
3111
|
logger.info({ tools: tools.length }, `${pkg.name} v${pkg.version} ready`);
|
|
3112
|
+
if (!process.env["SUPPRESS_BANNER"] && !process.env["CI"]) {
|
|
3113
|
+
process.stderr.write(
|
|
3114
|
+
`
|
|
3115
|
+
\u25B8 ${pkg.name} v${pkg.version} \u2014 ${tools.length} tools ready.
|
|
3116
|
+
if this helps you, a \u2B50 on github.com/Mogacode-ma/elementor-mcp-agent
|
|
3117
|
+
is the best thanks. (set SUPPRESS_BANNER=1 to silence this)
|
|
3118
|
+
|
|
3119
|
+
`
|
|
3120
|
+
);
|
|
3121
|
+
}
|
|
2958
3122
|
}
|
|
2959
3123
|
main().catch((e) => {
|
|
2960
3124
|
logger.error(e.message);
|