@sweepypanda/wp-elementor-mcp 1.7.1

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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +381 -0
  3. package/client-config.json +13 -0
  4. package/dist/elementor-handler.d.ts +51 -0
  5. package/dist/elementor-handler.d.ts.map +1 -0
  6. package/dist/elementor-handler.js +358 -0
  7. package/dist/elementor-handler.js.map +1 -0
  8. package/dist/helpers.d.ts +24 -0
  9. package/dist/helpers.d.ts.map +1 -0
  10. package/dist/helpers.js +107 -0
  11. package/dist/helpers.js.map +1 -0
  12. package/dist/index-backup.d.ts +3 -0
  13. package/dist/index-backup.d.ts.map +1 -0
  14. package/dist/index-backup.js +3752 -0
  15. package/dist/index-backup.js.map +1 -0
  16. package/dist/index.d.ts +3 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +187 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/server-config.d.ts +80 -0
  21. package/dist/server-config.d.ts.map +1 -0
  22. package/dist/server-config.js +137 -0
  23. package/dist/server-config.js.map +1 -0
  24. package/dist/tool-handlers.d.ts +44 -0
  25. package/dist/tool-handlers.d.ts.map +1 -0
  26. package/dist/tool-handlers.js +1682 -0
  27. package/dist/tool-handlers.js.map +1 -0
  28. package/dist/tool-schemas.d.ts +859 -0
  29. package/dist/tool-schemas.d.ts.map +1 -0
  30. package/dist/tool-schemas.js +870 -0
  31. package/dist/tool-schemas.js.map +1 -0
  32. package/dist/types.d.ts +19 -0
  33. package/dist/types.d.ts.map +1 -0
  34. package/dist/types.js +2 -0
  35. package/dist/types.js.map +1 -0
  36. package/dist/utils.d.ts +10 -0
  37. package/dist/utils.d.ts.map +1 -0
  38. package/dist/utils.js +142 -0
  39. package/dist/utils.js.map +1 -0
  40. package/dist/wordpress-client.d.ts +21 -0
  41. package/dist/wordpress-client.d.ts.map +1 -0
  42. package/dist/wordpress-client.js +151 -0
  43. package/dist/wordpress-client.js.map +1 -0
  44. package/package.json +74 -0
@@ -0,0 +1,3752 @@
1
+ #!/usr/bin/env node
2
+ // Load environment variables from .env file
3
+ import { config } from 'dotenv';
4
+ import { dirname, join } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ // Get the directory of this script for .env file path
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+ const envPath = join(__dirname, '..', '.env');
10
+ config({ path: envPath });
11
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
12
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
13
+ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema, InitializeRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
14
+ import axios from 'axios';
15
+ import FormData from 'form-data';
16
+ import https from 'https';
17
+ import { getServerConfig } from './server-config.js';
18
+ class ElementorWordPressMCP {
19
+ server;
20
+ axiosInstance = null;
21
+ config = null;
22
+ serverConfig;
23
+ constructor() {
24
+ this.serverConfig = getServerConfig();
25
+ this.server = new Server({
26
+ name: 'elementor-wordpress-mcp',
27
+ version: '1.7.1',
28
+ });
29
+ // Initialize WordPress configuration from environment variables
30
+ this.initializeFromEnvironment();
31
+ this.setupToolHandlers();
32
+ this.setupResourceHandlers();
33
+ }
34
+ // Robust JSON parsing utility for Elementor data
35
+ parseElementorResponse(responseText) {
36
+ try {
37
+ // Check if this is a direct API response
38
+ if (responseText.trim().startsWith('[') || responseText.trim().startsWith('{')) {
39
+ try {
40
+ const data = JSON.parse(responseText);
41
+ return {
42
+ success: true,
43
+ data: Array.isArray(data) ? data : [data]
44
+ };
45
+ }
46
+ catch (directParseError) {
47
+ return {
48
+ success: false,
49
+ error: `Direct JSON parse failed: ${directParseError}`,
50
+ rawData: responseText
51
+ };
52
+ }
53
+ }
54
+ // Extract debug info and JSON data from formatted response
55
+ let debugInfo = '';
56
+ let jsonData = '';
57
+ if (responseText.includes('--- Elementor Data ---')) {
58
+ const parts = responseText.split('--- Elementor Data ---');
59
+ debugInfo = parts[0]?.trim() || '';
60
+ jsonData = parts[1]?.trim() || '';
61
+ }
62
+ else if (responseText.includes('--- Raw Elementor Data ---')) {
63
+ const parts = responseText.split('--- Raw Elementor Data ---');
64
+ debugInfo = parts[0]?.trim() || '';
65
+ jsonData = parts[1]?.trim() || '';
66
+ }
67
+ else {
68
+ // Try to find JSON-like content
69
+ const jsonMatch = responseText.match(/(\[[\s\S]*\]|\{[\s\S]*\})/);
70
+ if (jsonMatch) {
71
+ jsonData = jsonMatch[1];
72
+ debugInfo = responseText.replace(jsonMatch[1], '').trim();
73
+ }
74
+ else {
75
+ return {
76
+ success: false,
77
+ error: 'No JSON data found in response',
78
+ rawData: responseText,
79
+ debugInfo: responseText
80
+ };
81
+ }
82
+ }
83
+ // Validate that we have data to parse
84
+ if (!jsonData || jsonData.trim() === '') {
85
+ return {
86
+ success: false,
87
+ error: 'Empty JSON data in response',
88
+ rawData: responseText,
89
+ debugInfo
90
+ };
91
+ }
92
+ // Check for known error messages in debug info
93
+ if (debugInfo.includes('No Elementor data found') ||
94
+ debugInfo.includes('does not use Elementor builder') ||
95
+ debugInfo.includes('failed to parse JSON')) {
96
+ return {
97
+ success: false,
98
+ error: 'No valid Elementor data available',
99
+ debugInfo,
100
+ rawData: jsonData
101
+ };
102
+ }
103
+ // Attempt to parse the JSON data
104
+ try {
105
+ const parsedData = JSON.parse(jsonData);
106
+ // Ensure we have an array
107
+ const dataArray = Array.isArray(parsedData) ? parsedData : [parsedData];
108
+ return {
109
+ success: true,
110
+ data: dataArray,
111
+ debugInfo
112
+ };
113
+ }
114
+ catch (parseError) {
115
+ // Try to clean the JSON and parse again
116
+ try {
117
+ // Remove common JSON formatting issues
118
+ const cleanedJson = jsonData
119
+ .replace(/^```json\s*/, '') // Remove markdown code blocks
120
+ .replace(/\s*```$/, '')
121
+ .replace(/^```\s*/, '')
122
+ .replace(/\n\s*\n/g, '\n') // Remove extra newlines
123
+ .trim();
124
+ const parsedData = JSON.parse(cleanedJson);
125
+ const dataArray = Array.isArray(parsedData) ? parsedData : [parsedData];
126
+ return {
127
+ success: true,
128
+ data: dataArray,
129
+ debugInfo
130
+ };
131
+ }
132
+ catch (cleanedParseError) {
133
+ return {
134
+ success: false,
135
+ error: `JSON parse failed: ${parseError.message}. Cleaned parse also failed: ${cleanedParseError}`,
136
+ rawData: jsonData,
137
+ debugInfo
138
+ };
139
+ }
140
+ }
141
+ }
142
+ catch (error) {
143
+ return {
144
+ success: false,
145
+ error: `Parsing utility failed: ${error.message}`,
146
+ rawData: responseText
147
+ };
148
+ }
149
+ }
150
+ // Safe method to get parsed Elementor data with comprehensive error handling
151
+ async safeGetElementorData(postId) {
152
+ try {
153
+ const response = await this.getElementorData({ post_id: postId });
154
+ // Parse the JSON response from content[0].text
155
+ const responseText = response.content[0].text;
156
+ const parsedResponse = JSON.parse(responseText);
157
+ // Check if response indicates success and has data
158
+ if (parsedResponse.status === 'success' && parsedResponse.data?.elementor_data) {
159
+ return {
160
+ success: true,
161
+ data: parsedResponse.data.elementor_data,
162
+ debugInfo: `Successfully retrieved ${parsedResponse.data.elementor_data.length} elements for post ${postId}`
163
+ };
164
+ }
165
+ else if (parsedResponse.status === 'error') {
166
+ return {
167
+ success: false,
168
+ error: parsedResponse.data?.message || parsedResponse.message || 'Unknown error from getElementorData',
169
+ debugInfo: parsedResponse.data?.details || parsedResponse.details || ''
170
+ };
171
+ }
172
+ else {
173
+ return {
174
+ success: false,
175
+ error: 'Unexpected response format from getElementorData',
176
+ debugInfo: `Response status: ${parsedResponse.status}`
177
+ };
178
+ }
179
+ }
180
+ catch (error) {
181
+ return {
182
+ success: false,
183
+ error: `Failed to retrieve Elementor data: ${error.message}`
184
+ };
185
+ }
186
+ }
187
+ initializeFromEnvironment() {
188
+ const baseUrl = process.env.WORDPRESS_BASE_URL;
189
+ const username = process.env.WORDPRESS_USERNAME;
190
+ const applicationPassword = process.env.WORDPRESS_APPLICATION_PASSWORD;
191
+ if (baseUrl && username && applicationPassword) {
192
+ console.error('Initializing WordPress connection from environment variables...');
193
+ this.setupAxios({
194
+ baseUrl: baseUrl.replace(/\/$/, ''), // Remove trailing slash if present
195
+ username,
196
+ applicationPassword
197
+ });
198
+ console.error('WordPress connection configured successfully');
199
+ }
200
+ else {
201
+ console.error('WordPress environment variables not found. Manual configuration will be required.');
202
+ console.error('Required environment variables:');
203
+ console.error('- WORDPRESS_BASE_URL');
204
+ console.error('- WORDPRESS_USERNAME');
205
+ console.error('- WORDPRESS_APPLICATION_PASSWORD');
206
+ }
207
+ }
208
+ setupAxios(config) {
209
+ const baseURL = config.baseUrl.endsWith('/')
210
+ ? `${config.baseUrl}wp-json/wp/v2/`
211
+ : `${config.baseUrl}/wp-json/wp/v2/`;
212
+ const auth = Buffer.from(`${config.username}:${config.applicationPassword}`).toString('base64');
213
+ const httpsAgent = new https.Agent({
214
+ rejectUnauthorized: this.shouldRejectUnauthorized(config.baseUrl)
215
+ });
216
+ this.axiosInstance = axios.create({
217
+ baseURL,
218
+ headers: {
219
+ 'Authorization': `Basic ${auth}`,
220
+ 'Content-Type': 'application/json',
221
+ },
222
+ httpsAgent: httpsAgent,
223
+ timeout: 60000, // Increased to 60 second timeout for large operations
224
+ maxContentLength: 50 * 1024 * 1024, // 50MB response limit
225
+ maxBodyLength: 10 * 1024 * 1024, // 10MB request limit
226
+ });
227
+ // Add request interceptor for debugging
228
+ this.axiosInstance.interceptors.request.use((config) => {
229
+ console.error(`Making request to: ${config.method?.toUpperCase()} ${config.url}`);
230
+ if (config.data && typeof config.data === 'string' && config.data.length > 1000) {
231
+ console.error(`Request data size: ${config.data.length} characters`);
232
+ }
233
+ return config;
234
+ }, (error) => {
235
+ console.error(`Request error: ${error.message}`);
236
+ return Promise.reject(error);
237
+ });
238
+ // Add response interceptor for debugging and error handling
239
+ this.axiosInstance.interceptors.response.use((response) => {
240
+ console.error(`Response received: ${response.status} ${response.statusText}`);
241
+ if (response.data && typeof response.data === 'string' && response.data.length > 10000) {
242
+ console.error(`Response data size: ${response.data.length} characters`);
243
+ }
244
+ else if (response.data && Array.isArray(response.data)) {
245
+ console.error(`Response array length: ${response.data.length} items`);
246
+ }
247
+ return response;
248
+ }, (error) => {
249
+ // Enhanced error logging
250
+ if (error.code === 'ECONNABORTED') {
251
+ console.error(`🕐 Request timeout: ${error.message}`);
252
+ }
253
+ else if (error.response?.status) {
254
+ console.error(`❌ HTTP Error: ${error.response.status} ${error.response.statusText}`);
255
+ console.error(`URL: ${error.config?.url}`);
256
+ if (error.response.data?.message) {
257
+ console.error(`WordPress Error: ${error.response.data.message}`);
258
+ }
259
+ }
260
+ else {
261
+ console.error(`🔥 Network/Connection Error: ${error.message}`);
262
+ }
263
+ return Promise.reject(error);
264
+ });
265
+ this.config = config;
266
+ }
267
+ shouldRejectUnauthorized(baseUrl) {
268
+ try {
269
+ const url = new URL(baseUrl);
270
+ // Allow self-signed certificates for local development
271
+ const isLocal = url.hostname === 'localhost' ||
272
+ url.hostname === '127.0.0.1' ||
273
+ url.hostname.endsWith('.local') ||
274
+ url.hostname.endsWith('.dev') ||
275
+ url.hostname.endsWith('.test');
276
+ if (isLocal) {
277
+ console.error(`🔓 Allowing self-signed certificates for local development site: ${url.hostname}`);
278
+ return false;
279
+ }
280
+ // For production sites, require valid certificates
281
+ console.error(`🔒 Requiring valid SSL certificates for production site: ${url.hostname}`);
282
+ return true;
283
+ }
284
+ catch (error) {
285
+ // If URL parsing fails, default to requiring valid certificates
286
+ console.error(`⚠️ Could not parse URL ${baseUrl}, defaulting to requiring valid SSL certificates`);
287
+ return true;
288
+ }
289
+ }
290
+ ensureAuthenticated() {
291
+ if (!this.axiosInstance || !this.config) {
292
+ return this.createErrorResponse('WordPress connection not configured. Please set environment variables: WORDPRESS_BASE_URL, WORDPRESS_USERNAME, WORDPRESS_APPLICATION_PASSWORD.', 'NOT_CONFIGURED', 'AUTHENTICATION_ERROR', 'Missing WordPress connection configuration');
293
+ }
294
+ return null; // null means authenticated successfully
295
+ }
296
+ // Utility method to handle large responses with better error reporting
297
+ async safeApiCall(operation, operationName, context = '') {
298
+ try {
299
+ const startTime = Date.now();
300
+ console.error(`🚀 Starting ${operationName}${context ? ` for ${context}` : ''}`);
301
+ const result = await operation();
302
+ const duration = Date.now() - startTime;
303
+ console.error(`✅ Completed ${operationName} in ${duration}ms`);
304
+ return result;
305
+ }
306
+ catch (error) {
307
+ console.error(`❌ Failed ${operationName}${context ? ` for ${context}` : ''}`);
308
+ // Note: This method now throws errors but they will be caught by calling methods
309
+ // that use status-based responses
310
+ if (error.code === 'ECONNABORTED') {
311
+ console.error(`🕐 Operation timed out after 60 seconds`);
312
+ throw new Error(`Request timeout: ${operationName} took longer than 60 seconds. This often indicates the data is too large or the server is overloaded.`);
313
+ }
314
+ else if (error.response?.status >= 500) {
315
+ console.error(`🔥 Server error: ${error.response.status} ${error.response.statusText}`);
316
+ throw new Error(`Server error during ${operationName}: ${error.response.status} ${error.response.statusText}. The WordPress server may be overloaded or misconfigured.`);
317
+ }
318
+ else if (error.response?.status === 413) {
319
+ console.error(`📦 Payload too large`);
320
+ throw new Error(`Request payload too large for ${operationName}. Try breaking the operation into smaller chunks.`);
321
+ }
322
+ else if (error.message?.includes('maxContentLength')) {
323
+ console.error(`📦 Response too large`);
324
+ throw new Error(`Response too large for ${operationName}. The data exceeds 50MB limit. Try using chunked operations or filtering the request.`);
325
+ }
326
+ else {
327
+ // Re-throw the original error
328
+ throw error;
329
+ }
330
+ }
331
+ }
332
+ setupToolHandlers() {
333
+ // Tool Consolidation (v1.6.9):
334
+ // - Removed: get_elementor_data_chunked (replaced by get_elementor_data_smart)
335
+ // - Removed: get_page_structure (redundant with get_elementor_structure_summary)
336
+ // - Removed: clear_elementor_cache_by_page (redundant with clear_elementor_cache)
337
+ // - Renamed: get_elementor_data_deep_chunked → get_elementor_data_smart
338
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
339
+ const tools = [];
340
+ // Add tools based on configuration
341
+ if (this.serverConfig.basicWordPressOperations) {
342
+ tools.push({
343
+ name: 'get_posts',
344
+ description: 'Retrieve WordPress posts with optional filtering',
345
+ inputSchema: {
346
+ type: 'object',
347
+ properties: {
348
+ per_page: {
349
+ type: 'number',
350
+ description: 'Number of posts to retrieve (default: 10)',
351
+ default: 10,
352
+ },
353
+ status: {
354
+ type: 'string',
355
+ description: 'Post status (publish, draft, private, etc.)',
356
+ default: 'publish',
357
+ },
358
+ search: {
359
+ type: 'string',
360
+ description: 'Search term to filter posts',
361
+ },
362
+ },
363
+ },
364
+ }, {
365
+ name: 'get_post',
366
+ description: 'Get a specific WordPress post by ID',
367
+ inputSchema: {
368
+ type: 'object',
369
+ properties: {
370
+ id: {
371
+ type: 'number',
372
+ description: 'Post ID',
373
+ },
374
+ },
375
+ required: ['id'],
376
+ },
377
+ }, {
378
+ name: 'create_post',
379
+ description: 'Create a new WordPress post',
380
+ inputSchema: {
381
+ type: 'object',
382
+ properties: {
383
+ title: {
384
+ type: 'string',
385
+ description: 'Post title',
386
+ },
387
+ content: {
388
+ type: 'string',
389
+ description: 'Post content (HTML)',
390
+ },
391
+ status: {
392
+ type: 'string',
393
+ description: 'Post status (draft, publish, private)',
394
+ default: 'draft',
395
+ },
396
+ excerpt: {
397
+ type: 'string',
398
+ description: 'Post excerpt',
399
+ },
400
+ },
401
+ required: ['title', 'content'],
402
+ },
403
+ }, {
404
+ name: 'update_post',
405
+ description: 'Update an existing WordPress post',
406
+ inputSchema: {
407
+ type: 'object',
408
+ properties: {
409
+ id: {
410
+ type: 'number',
411
+ description: 'Post ID to update',
412
+ },
413
+ title: {
414
+ type: 'string',
415
+ description: 'Post title',
416
+ },
417
+ content: {
418
+ type: 'string',
419
+ description: 'Post content (HTML)',
420
+ },
421
+ status: {
422
+ type: 'string',
423
+ description: 'Post status (draft, publish, private)',
424
+ },
425
+ excerpt: {
426
+ type: 'string',
427
+ description: 'Post excerpt',
428
+ },
429
+ },
430
+ required: ['id'],
431
+ },
432
+ }, {
433
+ name: 'get_pages',
434
+ description: 'Retrieve WordPress pages',
435
+ inputSchema: {
436
+ type: 'object',
437
+ properties: {
438
+ per_page: {
439
+ type: 'number',
440
+ description: 'Number of pages to retrieve (default: 10)',
441
+ default: 10,
442
+ },
443
+ status: {
444
+ type: 'string',
445
+ description: 'Page status (publish, draft, private, etc.)',
446
+ default: 'publish',
447
+ },
448
+ },
449
+ },
450
+ }, {
451
+ name: 'get_page',
452
+ description: 'Get a specific WordPress page by ID',
453
+ inputSchema: {
454
+ type: 'object',
455
+ properties: {
456
+ id: {
457
+ type: 'number',
458
+ description: 'Page ID',
459
+ },
460
+ },
461
+ required: ['id'],
462
+ },
463
+ }, {
464
+ name: 'list_all_content',
465
+ description: 'List all posts and pages with their IDs and Elementor status for debugging',
466
+ inputSchema: {
467
+ type: 'object',
468
+ properties: {
469
+ per_page: {
470
+ type: 'number',
471
+ description: 'Number of items to retrieve per type (default: 50)',
472
+ default: 50,
473
+ },
474
+ include_all_statuses: {
475
+ type: 'boolean',
476
+ description: 'Include draft, private, and trashed content (default: false)',
477
+ default: false,
478
+ },
479
+ },
480
+ },
481
+ }, {
482
+ name: 'create_page',
483
+ description: 'Create a new WordPress page',
484
+ inputSchema: {
485
+ type: 'object',
486
+ properties: {
487
+ title: {
488
+ type: 'string',
489
+ description: 'Page title',
490
+ },
491
+ content: {
492
+ type: 'string',
493
+ description: 'Page content (HTML)',
494
+ },
495
+ status: {
496
+ type: 'string',
497
+ description: 'Page status (draft, publish, private)',
498
+ default: 'draft',
499
+ },
500
+ excerpt: {
501
+ type: 'string',
502
+ description: 'Page excerpt',
503
+ },
504
+ parent: {
505
+ type: 'number',
506
+ description: 'Parent page ID (for creating child pages)',
507
+ },
508
+ },
509
+ required: ['title', 'content'],
510
+ },
511
+ }, {
512
+ name: 'update_page',
513
+ description: 'Update an existing WordPress page',
514
+ inputSchema: {
515
+ type: 'object',
516
+ properties: {
517
+ id: {
518
+ type: 'number',
519
+ description: 'Page ID to update',
520
+ },
521
+ title: {
522
+ type: 'string',
523
+ description: 'Page title',
524
+ },
525
+ content: {
526
+ type: 'string',
527
+ description: 'Page content (HTML)',
528
+ },
529
+ status: {
530
+ type: 'string',
531
+ description: 'Page status (draft, publish, private)',
532
+ },
533
+ excerpt: {
534
+ type: 'string',
535
+ description: 'Page excerpt',
536
+ },
537
+ parent: {
538
+ type: 'number',
539
+ description: 'Parent page ID (for creating child pages)',
540
+ },
541
+ },
542
+ required: ['id'],
543
+ },
544
+ }, {
545
+ name: 'get_media',
546
+ description: 'Get WordPress media library items',
547
+ inputSchema: {
548
+ type: 'object',
549
+ properties: {
550
+ per_page: {
551
+ type: 'number',
552
+ description: 'Number of media items to retrieve (default: 10)',
553
+ default: 10,
554
+ },
555
+ media_type: {
556
+ type: 'string',
557
+ description: 'Media type (image, video, audio, etc.)',
558
+ },
559
+ },
560
+ },
561
+ }, {
562
+ name: 'upload_media',
563
+ description: 'Upload media file to WordPress',
564
+ inputSchema: {
565
+ type: 'object',
566
+ properties: {
567
+ file_path: {
568
+ type: 'string',
569
+ description: 'Local path to file to upload',
570
+ },
571
+ title: {
572
+ type: 'string',
573
+ description: 'Media title',
574
+ },
575
+ alt_text: {
576
+ type: 'string',
577
+ description: 'Alt text for images',
578
+ },
579
+ },
580
+ required: ['file_path'],
581
+ },
582
+ });
583
+ }
584
+ if (this.serverConfig.basicElementorOperations) {
585
+ tools.push({
586
+ name: 'get_elementor_templates',
587
+ description: 'Get Elementor templates',
588
+ inputSchema: {
589
+ type: 'object',
590
+ properties: {
591
+ per_page: {
592
+ type: 'number',
593
+ description: 'Number of templates to retrieve (default: 10)',
594
+ default: 10,
595
+ },
596
+ type: {
597
+ type: 'string',
598
+ description: 'Template type (page, section, widget, etc.)',
599
+ },
600
+ },
601
+ },
602
+ }, {
603
+ name: 'get_elementor_data',
604
+ description: 'Get complete Elementor data for a page',
605
+ inputSchema: {
606
+ type: 'object',
607
+ properties: {
608
+ post_id: {
609
+ type: 'number',
610
+ description: 'Post/Page ID',
611
+ },
612
+ },
613
+ required: ['post_id'],
614
+ },
615
+ }, {
616
+ name: 'update_elementor_data',
617
+ description: 'Update complete Elementor data for a page',
618
+ inputSchema: {
619
+ type: 'object',
620
+ properties: {
621
+ post_id: {
622
+ type: 'number',
623
+ description: 'Post/Page ID to update',
624
+ },
625
+ elementor_data: {
626
+ type: 'string',
627
+ description: 'Complete Elementor data as JSON string',
628
+ },
629
+ },
630
+ required: ['post_id', 'elementor_data'],
631
+ },
632
+ }, {
633
+ name: 'update_elementor_widget',
634
+ description: 'Update a specific widget within an Elementor page (incremental update)',
635
+ inputSchema: {
636
+ type: 'object',
637
+ properties: {
638
+ post_id: {
639
+ type: 'number',
640
+ description: 'Post/Page ID to update',
641
+ },
642
+ widget_id: {
643
+ type: 'string',
644
+ description: 'Elementor widget ID (e.g., "621ef73f")',
645
+ },
646
+ widget_settings: {
647
+ type: 'object',
648
+ description: 'Widget settings object to update',
649
+ },
650
+ widget_content: {
651
+ type: 'string',
652
+ description: 'Widget content (for widgets like HTML, text, etc.)',
653
+ },
654
+ },
655
+ required: ['post_id', 'widget_id'],
656
+ },
657
+ }, {
658
+ name: 'get_elementor_widget',
659
+ description: 'Get a specific widget from an Elementor page',
660
+ inputSchema: {
661
+ type: 'object',
662
+ properties: {
663
+ post_id: {
664
+ type: 'number',
665
+ description: 'Post/Page ID',
666
+ },
667
+ widget_id: {
668
+ type: 'string',
669
+ description: 'Elementor widget ID (e.g., "621ef73f")',
670
+ },
671
+ },
672
+ required: ['post_id', 'widget_id'],
673
+ },
674
+ }, {
675
+ name: 'get_elementor_elements',
676
+ description: 'Get a simplified list of all elements and their IDs from an Elementor page',
677
+ inputSchema: {
678
+ type: 'object',
679
+ properties: {
680
+ post_id: {
681
+ type: 'number',
682
+ description: 'Post/Page ID',
683
+ },
684
+ include_content: {
685
+ type: 'boolean',
686
+ description: 'Include element content/settings preview (default: false)',
687
+ default: false,
688
+ },
689
+ },
690
+ required: ['post_id'],
691
+ },
692
+ }, {
693
+ name: 'update_elementor_section',
694
+ description: 'Update multiple widgets within a specific Elementor section (batch update)',
695
+ inputSchema: {
696
+ type: 'object',
697
+ properties: {
698
+ post_id: {
699
+ type: 'number',
700
+ description: 'Post/Page ID to update',
701
+ },
702
+ section_id: {
703
+ type: 'string',
704
+ description: 'Elementor section ID',
705
+ },
706
+ widgets_updates: {
707
+ type: 'array',
708
+ description: 'Array of widget updates',
709
+ items: {
710
+ type: 'object',
711
+ properties: {
712
+ widget_id: {
713
+ type: 'string',
714
+ description: 'Widget ID to update',
715
+ },
716
+ widget_settings: {
717
+ type: 'object',
718
+ description: 'Widget settings to update',
719
+ },
720
+ widget_content: {
721
+ type: 'string',
722
+ description: 'Widget content to update',
723
+ },
724
+ },
725
+ required: ['widget_id'],
726
+ },
727
+ },
728
+ },
729
+ required: ['post_id', 'section_id', 'widgets_updates'],
730
+ },
731
+ }, {
732
+ name: 'get_elementor_data_smart',
733
+ description: 'Get Elementor data with intelligent chunking for large pages - automatically handles nested structures and token limits',
734
+ inputSchema: {
735
+ type: 'object',
736
+ properties: {
737
+ post_id: {
738
+ type: 'number',
739
+ description: 'Post/Page ID',
740
+ },
741
+ max_depth: {
742
+ type: 'number',
743
+ description: 'Maximum nesting depth to include (default: 2 - sections and columns only)',
744
+ default: 2,
745
+ },
746
+ element_index: {
747
+ type: 'number',
748
+ description: 'Zero-based index of top-level element to retrieve (default: 0)',
749
+ default: 0,
750
+ },
751
+ include_widget_previews: {
752
+ type: 'boolean',
753
+ description: 'Include widget content previews (default: false)',
754
+ default: false,
755
+ },
756
+ },
757
+ required: ['post_id'],
758
+ },
759
+ }, {
760
+ name: 'get_elementor_structure_summary',
761
+ description: 'Get a compact summary of the page structure without heavy content to understand layout quickly',
762
+ inputSchema: {
763
+ type: 'object',
764
+ properties: {
765
+ post_id: {
766
+ type: 'number',
767
+ description: 'Post/Page ID',
768
+ },
769
+ max_depth: {
770
+ type: 'number',
771
+ description: 'Maximum depth to analyze (default: 4)',
772
+ default: 4,
773
+ },
774
+ },
775
+ required: ['post_id'],
776
+ },
777
+ }, {
778
+ name: 'backup_elementor_data',
779
+ description: 'Create a backup of Elementor page data',
780
+ inputSchema: {
781
+ type: 'object',
782
+ properties: {
783
+ post_id: {
784
+ type: 'number',
785
+ description: 'Post/Page ID to backup',
786
+ },
787
+ backup_name: {
788
+ type: 'string',
789
+ description: 'Optional name for the backup',
790
+ },
791
+ },
792
+ required: ['post_id'],
793
+ },
794
+ }, {
795
+ name: 'clear_elementor_cache',
796
+ description: 'Clear Elementor cache for better performance',
797
+ inputSchema: {
798
+ type: 'object',
799
+ properties: {
800
+ post_id: {
801
+ type: 'number',
802
+ description: 'Optional: specific post ID to clear cache for',
803
+ },
804
+ },
805
+ },
806
+ });
807
+ }
808
+ if (this.serverConfig.sectionContainerCreation) {
809
+ tools.push({
810
+ name: 'create_elementor_section',
811
+ description: 'Create a new Elementor section with specified columns',
812
+ inputSchema: {
813
+ type: 'object',
814
+ properties: {
815
+ post_id: {
816
+ type: 'number',
817
+ description: 'Post/Page ID to add section to',
818
+ },
819
+ position: {
820
+ type: 'number',
821
+ description: 'Position to insert the section (0-based, default: append to end)',
822
+ },
823
+ columns: {
824
+ type: 'number',
825
+ description: 'Number of columns to create (default: 1)',
826
+ default: 1,
827
+ },
828
+ section_settings: {
829
+ type: 'object',
830
+ description: 'Optional settings for the section',
831
+ },
832
+ },
833
+ required: ['post_id'],
834
+ },
835
+ }, {
836
+ name: 'create_elementor_container',
837
+ description: 'Create a new Elementor container (Flexbox)',
838
+ inputSchema: {
839
+ type: 'object',
840
+ properties: {
841
+ post_id: {
842
+ type: 'number',
843
+ description: 'Post/Page ID to add container to',
844
+ },
845
+ position: {
846
+ type: 'number',
847
+ description: 'Position to insert the container (0-based, default: append to end)',
848
+ },
849
+ container_settings: {
850
+ type: 'object',
851
+ description: 'Optional settings for the container',
852
+ },
853
+ },
854
+ required: ['post_id'],
855
+ },
856
+ }, {
857
+ name: 'add_column_to_section',
858
+ description: 'Add columns to an existing Elementor section',
859
+ inputSchema: {
860
+ type: 'object',
861
+ properties: {
862
+ post_id: {
863
+ type: 'number',
864
+ description: 'Post/Page ID',
865
+ },
866
+ section_id: {
867
+ type: 'string',
868
+ description: 'Section ID to add columns to',
869
+ },
870
+ columns_to_add: {
871
+ type: 'number',
872
+ description: 'Number of columns to add (default: 1)',
873
+ default: 1,
874
+ },
875
+ },
876
+ required: ['post_id', 'section_id'],
877
+ },
878
+ }, {
879
+ name: 'duplicate_section',
880
+ description: 'Duplicate an existing Elementor section',
881
+ inputSchema: {
882
+ type: 'object',
883
+ properties: {
884
+ post_id: {
885
+ type: 'number',
886
+ description: 'Post/Page ID',
887
+ },
888
+ section_id: {
889
+ type: 'string',
890
+ description: 'Section ID to duplicate',
891
+ },
892
+ position: {
893
+ type: 'number',
894
+ description: 'Position to insert the duplicated section (0-based, default: after original)',
895
+ },
896
+ },
897
+ required: ['post_id', 'section_id'],
898
+ },
899
+ });
900
+ }
901
+ if (this.serverConfig.widgetAddition) {
902
+ tools.push({
903
+ name: 'add_widget_to_section',
904
+ description: 'Add a widget to a specific section/column',
905
+ inputSchema: {
906
+ type: 'object',
907
+ properties: {
908
+ post_id: {
909
+ type: 'number',
910
+ description: 'Post/Page ID',
911
+ },
912
+ section_id: {
913
+ type: 'string',
914
+ description: 'Target section ID (optional if column_id is provided)',
915
+ },
916
+ column_id: {
917
+ type: 'string',
918
+ description: 'Target column ID (optional if section_id is provided)',
919
+ },
920
+ widget_type: {
921
+ type: 'string',
922
+ description: 'Widget type (e.g., "heading", "text", "image", "button")',
923
+ },
924
+ widget_settings: {
925
+ type: 'object',
926
+ description: 'Widget settings and content',
927
+ },
928
+ position: {
929
+ type: 'number',
930
+ description: 'Position within the container (0-based, default: append)',
931
+ },
932
+ },
933
+ required: ['post_id', 'widget_type'],
934
+ },
935
+ }, {
936
+ name: 'insert_widget_at_position',
937
+ description: 'Insert a widget at a specific position relative to another element',
938
+ inputSchema: {
939
+ type: 'object',
940
+ properties: {
941
+ post_id: {
942
+ type: 'number',
943
+ description: 'Post/Page ID',
944
+ },
945
+ widget_type: {
946
+ type: 'string',
947
+ description: 'Widget type to insert',
948
+ },
949
+ widget_settings: {
950
+ type: 'object',
951
+ description: 'Widget settings and content',
952
+ },
953
+ target_element_id: {
954
+ type: 'string',
955
+ description: 'Element ID to insert relative to',
956
+ },
957
+ insert_position: {
958
+ type: 'string',
959
+ description: 'Position relative to target: "before", "after", "inside" (default: "after")',
960
+ default: 'after',
961
+ },
962
+ },
963
+ required: ['post_id', 'widget_type', 'target_element_id'],
964
+ },
965
+ }, {
966
+ name: 'clone_widget',
967
+ description: 'Clone an existing widget',
968
+ inputSchema: {
969
+ type: 'object',
970
+ properties: {
971
+ post_id: {
972
+ type: 'number',
973
+ description: 'Post/Page ID',
974
+ },
975
+ widget_id: {
976
+ type: 'string',
977
+ description: 'Widget ID to clone',
978
+ },
979
+ target_element_id: {
980
+ type: 'string',
981
+ description: 'Element ID to insert cloned widget relative to (optional)',
982
+ },
983
+ insert_position: {
984
+ type: 'string',
985
+ description: 'Position relative to target: "before", "after", "inside" (default: "after")',
986
+ default: 'after',
987
+ },
988
+ },
989
+ required: ['post_id', 'widget_id'],
990
+ },
991
+ }, {
992
+ name: 'move_widget',
993
+ description: 'Move a widget to a different section/column',
994
+ inputSchema: {
995
+ type: 'object',
996
+ properties: {
997
+ post_id: {
998
+ type: 'number',
999
+ description: 'Post/Page ID',
1000
+ },
1001
+ widget_id: {
1002
+ type: 'string',
1003
+ description: 'Widget ID to move',
1004
+ },
1005
+ target_section_id: {
1006
+ type: 'string',
1007
+ description: 'Target section ID (optional if target_column_id is provided)',
1008
+ },
1009
+ target_column_id: {
1010
+ type: 'string',
1011
+ description: 'Target column ID (optional if target_section_id is provided)',
1012
+ },
1013
+ position: {
1014
+ type: 'number',
1015
+ description: 'Position within target container (0-based, default: append)',
1016
+ },
1017
+ },
1018
+ required: ['post_id', 'widget_id'],
1019
+ },
1020
+ });
1021
+ }
1022
+ if (this.serverConfig.elementManagement) {
1023
+ tools.push({
1024
+ name: 'delete_elementor_element',
1025
+ description: 'Delete an Elementor element (section, column, or widget)',
1026
+ inputSchema: {
1027
+ type: 'object',
1028
+ properties: {
1029
+ post_id: {
1030
+ type: 'number',
1031
+ description: 'Post/Page ID',
1032
+ },
1033
+ element_id: {
1034
+ type: 'string',
1035
+ description: 'Element ID to delete',
1036
+ },
1037
+ },
1038
+ required: ['post_id', 'element_id'],
1039
+ },
1040
+ }, {
1041
+ name: 'reorder_elements',
1042
+ description: 'Reorder elements within a container',
1043
+ inputSchema: {
1044
+ type: 'object',
1045
+ properties: {
1046
+ post_id: {
1047
+ type: 'number',
1048
+ description: 'Post/Page ID',
1049
+ },
1050
+ container_id: {
1051
+ type: 'string',
1052
+ description: 'Container ID (section or column)',
1053
+ },
1054
+ element_ids: {
1055
+ type: 'array',
1056
+ description: 'Array of element IDs in desired order',
1057
+ items: {
1058
+ type: 'string',
1059
+ },
1060
+ },
1061
+ },
1062
+ required: ['post_id', 'container_id', 'element_ids'],
1063
+ },
1064
+ }, {
1065
+ name: 'copy_element_settings',
1066
+ description: 'Copy settings from one element to another',
1067
+ inputSchema: {
1068
+ type: 'object',
1069
+ properties: {
1070
+ post_id: {
1071
+ type: 'number',
1072
+ description: 'Post/Page ID',
1073
+ },
1074
+ source_element_id: {
1075
+ type: 'string',
1076
+ description: 'Source element ID to copy from',
1077
+ },
1078
+ target_element_id: {
1079
+ type: 'string',
1080
+ description: 'Target element ID to copy to',
1081
+ },
1082
+ settings_to_copy: {
1083
+ type: 'array',
1084
+ description: 'Specific setting keys to copy (optional, copies all if not specified)',
1085
+ items: {
1086
+ type: 'string',
1087
+ },
1088
+ },
1089
+ },
1090
+ required: ['post_id', 'source_element_id', 'target_element_id'],
1091
+ },
1092
+ });
1093
+ }
1094
+ if (this.serverConfig.advancedElementOperations) {
1095
+ tools.push({
1096
+ name: 'find_elements_by_type',
1097
+ description: 'Find all elements of a specific type on a page',
1098
+ inputSchema: {
1099
+ type: 'object',
1100
+ properties: {
1101
+ post_id: {
1102
+ type: 'number',
1103
+ description: 'Post/Page ID',
1104
+ },
1105
+ widget_type: {
1106
+ type: 'string',
1107
+ description: 'Widget type to search for (e.g., "heading", "text", "image")',
1108
+ },
1109
+ include_settings: {
1110
+ type: 'boolean',
1111
+ description: 'Include element settings in results (default: false)',
1112
+ default: false,
1113
+ },
1114
+ },
1115
+ required: ['post_id', 'widget_type'],
1116
+ },
1117
+ });
1118
+ }
1119
+ // Add tool count to help users understand which mode is active
1120
+ const totalEnabled = this.serverConfig.getTotalEnabledFeatures();
1121
+ if (process.env.NODE_ENV !== 'production') {
1122
+ console.error(`[Config] Loaded ${tools.length} tools (Mode: ${this.serverConfig.mode}, Features: ${totalEnabled})`);
1123
+ console.error(`[Config] Active features: ${Object.entries(this.serverConfig)
1124
+ .filter(([key, value]) => typeof value === 'boolean' && value && key !== 'mode')
1125
+ .map(([key]) => key)
1126
+ .join(', ')}`);
1127
+ }
1128
+ return { tools };
1129
+ });
1130
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
1131
+ const { name, arguments: args } = request.params;
1132
+ try {
1133
+ switch (name) {
1134
+ case 'get_posts':
1135
+ return await this.getPosts(args);
1136
+ case 'get_post':
1137
+ return await this.getPost(args);
1138
+ case 'create_post':
1139
+ return await this.createPost(args);
1140
+ case 'update_post':
1141
+ return await this.updatePost(args);
1142
+ case 'get_pages':
1143
+ return await this.getPages(args);
1144
+ case 'get_page':
1145
+ return await this.getPage(args);
1146
+ case 'list_all_content':
1147
+ return await this.listAllContent(args);
1148
+ case 'create_page':
1149
+ return await this.createPage(args);
1150
+ case 'update_page':
1151
+ return await this.updatePage(args);
1152
+ case 'get_elementor_templates':
1153
+ return await this.getElementorTemplates(args);
1154
+ case 'get_elementor_data':
1155
+ return await this.getElementorData(args);
1156
+ case 'update_elementor_data':
1157
+ return await this.updateElementorData(args);
1158
+ case 'update_elementor_widget':
1159
+ return await this.updateElementorWidget(args);
1160
+ case 'get_elementor_widget':
1161
+ return await this.getElementorWidget(args);
1162
+ case 'get_elementor_elements':
1163
+ return await this.getElementorElements(args);
1164
+ case 'update_elementor_section':
1165
+ return await this.updateElementorSection(args);
1166
+ case 'get_elementor_data_smart':
1167
+ return await this.getElementorDataDeepChunked(args);
1168
+ case 'get_elementor_structure_summary':
1169
+ return await this.getElementorStructureSummary(args);
1170
+ case 'backup_elementor_data':
1171
+ return await this.backupElementorData(args);
1172
+ case 'get_media':
1173
+ return await this.getMedia(args);
1174
+ case 'upload_media':
1175
+ return await this.uploadMedia(args);
1176
+ // Section and Container Creation Tools
1177
+ case 'create_elementor_section':
1178
+ return await this.createElementorSection(args);
1179
+ case 'create_elementor_container':
1180
+ return await this.createElementorContainer(args);
1181
+ case 'add_column_to_section':
1182
+ return await this.addColumnToSection(args);
1183
+ case 'duplicate_section':
1184
+ return await this.duplicateSection(args);
1185
+ // Widget Addition Tools
1186
+ case 'add_widget_to_section':
1187
+ return await this.addWidgetToSection(args);
1188
+ case 'insert_widget_at_position':
1189
+ return await this.insertWidgetAtPosition(args);
1190
+ case 'clone_widget':
1191
+ return await this.cloneWidget(args);
1192
+ case 'move_widget':
1193
+ return await this.moveWidget(args);
1194
+ // Element Management Tools
1195
+ case 'delete_elementor_element':
1196
+ return await this.deleteElementorElement(args);
1197
+ case 'reorder_elements':
1198
+ return await this.reorderElements(args);
1199
+ case 'copy_element_settings':
1200
+ return await this.copyElementSettings(args);
1201
+ // Template Management
1202
+ case 'create_elementor_template':
1203
+ return await this.createElementorTemplate(args);
1204
+ case 'apply_template_to_page':
1205
+ return await this.applyTemplateToPage(args);
1206
+ case 'export_elementor_template':
1207
+ return await this.exportElementorTemplate(args);
1208
+ case 'import_elementor_template':
1209
+ return await this.importElementorTemplate(args);
1210
+ // Global Settings Management
1211
+ case 'get_elementor_global_colors':
1212
+ return await this.getElementorGlobalColors(args);
1213
+ case 'update_elementor_global_colors':
1214
+ return await this.updateElementorGlobalColors(args);
1215
+ case 'get_elementor_global_fonts':
1216
+ return await this.getElementorGlobalFonts(args);
1217
+ case 'update_elementor_global_fonts':
1218
+ return await this.updateElementorGlobalFonts(args);
1219
+ // Page Structure Tools
1220
+ case 'rebuild_page_structure':
1221
+ return await this.rebuildPageStructure(args);
1222
+ case 'validate_elementor_data':
1223
+ return await this.validateElementorData(args);
1224
+ // Performance & Optimization
1225
+ case 'regenerate_css':
1226
+ return await this.regenerateCSS(args);
1227
+ case 'optimize_elementor_assets':
1228
+ return await this.optimizeElementorAssets(args);
1229
+ case 'clear_elementor_cache':
1230
+ return await this.clearElementorCacheGeneral(args);
1231
+ // Advanced Element Operations
1232
+ case 'find_elements_by_type':
1233
+ return await this.findElementsByType(args);
1234
+ case 'bulk_update_widget_settings':
1235
+ return await this.bulkUpdateWidgetSettings(args);
1236
+ case 'replace_widget_content':
1237
+ return await this.replaceWidgetContent(args);
1238
+ // Custom Fields Integration
1239
+ case 'get_elementor_custom_fields':
1240
+ return await this.getElementorCustomFields(args);
1241
+ case 'update_dynamic_content_sources':
1242
+ return await this.updateDynamicContentSources(args);
1243
+ // Revision and History
1244
+ case 'get_elementor_revisions':
1245
+ return await this.getElementorRevisions(args);
1246
+ case 'restore_elementor_revision':
1247
+ return await this.restoreElementorRevision(args);
1248
+ case 'compare_elementor_revisions':
1249
+ return await this.compareElementorRevisions(args);
1250
+ default:
1251
+ return this.createErrorResponse(`Unknown tool: ${name}`, 'METHOD_NOT_FOUND', 'TOOL_ERROR', `Tool "${name}" is not implemented or available`);
1252
+ }
1253
+ }
1254
+ catch (error) {
1255
+ if (error instanceof McpError) {
1256
+ // Convert MCP errors to structured format
1257
+ return this.createErrorResponse(error.message || 'MCP operation failed', 'MCP_ERROR', 'PROTOCOL_ERROR', `MCP Error Code: ${error.code || 'Unknown'}`);
1258
+ }
1259
+ return this.createErrorResponse(`Tool execution failed: ${error instanceof Error ? error.message : String(error)}`, 'EXECUTION_ERROR', 'INTERNAL_ERROR', 'Unexpected error during tool execution');
1260
+ }
1261
+ });
1262
+ this.server.setRequestHandler(InitializeRequestSchema, async (request) => {
1263
+ return {
1264
+ protocolVersion: request.params.protocolVersion,
1265
+ capabilities: {
1266
+ tools: {},
1267
+ },
1268
+ serverInfo: {
1269
+ name: 'elementor-wordpress-mcp',
1270
+ version: '1.7.1',
1271
+ },
1272
+ };
1273
+ });
1274
+ }
1275
+ setupResourceHandlers() {
1276
+ this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
1277
+ return {
1278
+ resources: [
1279
+ {
1280
+ uri: 'elementor://config',
1281
+ mimeType: 'application/json',
1282
+ name: 'WordPress Configuration',
1283
+ description: 'Current WordPress connection configuration',
1284
+ },
1285
+ ],
1286
+ };
1287
+ });
1288
+ this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
1289
+ const { uri } = request.params;
1290
+ if (uri === 'elementor://config') {
1291
+ return {
1292
+ contents: [
1293
+ {
1294
+ uri,
1295
+ mimeType: 'application/json',
1296
+ text: JSON.stringify(this.config
1297
+ ? {
1298
+ baseUrl: this.config.baseUrl,
1299
+ username: this.config.username,
1300
+ connected: true,
1301
+ }
1302
+ : { connected: false }, null, 2),
1303
+ },
1304
+ ],
1305
+ };
1306
+ }
1307
+ return this.createErrorResponse(`Unknown resource: ${uri}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
1308
+ });
1309
+ }
1310
+ // Tool implementations
1311
+ async getPosts(args) {
1312
+ const authCheck = this.ensureAuthenticated();
1313
+ if (authCheck)
1314
+ return authCheck; // Return error response if not authenticated
1315
+ const params = {
1316
+ per_page: args.per_page || 10,
1317
+ status: args.status || 'publish',
1318
+ context: 'view' // Use 'view' instead of 'edit' for lighter responses
1319
+ };
1320
+ if (args.search) {
1321
+ params.search = args.search;
1322
+ }
1323
+ try {
1324
+ console.error(`Fetching posts with params: ${JSON.stringify(params)}`);
1325
+ const response = await this.axiosInstance.get('posts', { params });
1326
+ // Create optimized summary objects instead of returning full content
1327
+ const posts = response.data;
1328
+ const postSummaries = posts.map((post) => ({
1329
+ id: post.id,
1330
+ title: post.title?.rendered || '(No title)',
1331
+ status: post.status,
1332
+ date_created: post.date,
1333
+ date_modified: post.modified,
1334
+ url: post.link,
1335
+ excerpt: post.excerpt?.rendered ? post.excerpt.rendered.replace(/<[^>]*>/g, '').substring(0, 100) + '...' : '',
1336
+ author: post.author,
1337
+ featured_media: post.featured_media,
1338
+ comment_count: post.comment_count || 0,
1339
+ // Include Elementor status for quick reference
1340
+ elementor_status: post.meta?._elementor_data ? 'full' :
1341
+ post.meta?._elementor_edit_mode === 'builder' ? 'partial' : 'none'
1342
+ }));
1343
+ let debugInfo = `Found ${posts.length} posts\n`;
1344
+ // Add summary with Elementor status counts
1345
+ const elementorStats = {
1346
+ full: postSummaries.filter((p) => p.elementor_status === 'full').length,
1347
+ partial: postSummaries.filter((p) => p.elementor_status === 'partial').length,
1348
+ none: postSummaries.filter((p) => p.elementor_status === 'none').length
1349
+ };
1350
+ debugInfo += `Elementor Status: ✅ Full (${elementorStats.full}), ⚠️ Partial (${elementorStats.partial}), ❌ None (${elementorStats.none})\n`;
1351
+ debugInfo += `\n💡 Use get_post tool with specific ID for full content details\n`;
1352
+ return this.createSuccessResponse({
1353
+ posts: postSummaries, // Return summaries instead of full objects
1354
+ summary: debugInfo,
1355
+ total_found: posts.length,
1356
+ performance_note: "Optimized response - use get_post(id) for full content details"
1357
+ }, `Successfully retrieved ${posts.length} post summaries`);
1358
+ }
1359
+ catch (error) {
1360
+ console.error(`Error fetching posts: ${error.response?.status} - ${error.response?.statusText}`);
1361
+ console.error(`URL: ${error.config?.url}`);
1362
+ console.error(`Headers: ${JSON.stringify(error.config?.headers)}`);
1363
+ return this.createErrorResponse(`Failed to fetch posts: ${error.response?.status} ${error.response?.statusText} - ${error.response?.data?.message || error.message}`, 'FETCH_POSTS_ERROR', 'API_ERROR', `HTTP ${error.response?.status}: ${error.response?.statusText}`);
1364
+ }
1365
+ }
1366
+ async getPost(args) {
1367
+ const authCheck = this.ensureAuthenticated();
1368
+ if (authCheck)
1369
+ return authCheck;
1370
+ try {
1371
+ console.error(`Fetching post with ID: ${args.id}`);
1372
+ const response = await this.axiosInstance.get(`posts/${args.id}`, {
1373
+ params: { context: 'edit' }
1374
+ });
1375
+ return this.createSuccessResponse(response.data, `Successfully retrieved post ${args.id}`);
1376
+ }
1377
+ catch (error) {
1378
+ console.error(`Error fetching post ${args.id}: ${error.response?.status} - ${error.response?.statusText}`);
1379
+ console.error(`URL: ${error.config?.url}`);
1380
+ console.error(`Headers: ${JSON.stringify(error.config?.headers)}`);
1381
+ if (error.response?.status === 404) {
1382
+ return this.createErrorResponse(`Post with ID ${args.id} not found. The post may have been deleted, be in trash, or may not exist.`, 'POST_NOT_FOUND', 'NOT_FOUND_ERROR', `HTTP 404: Post ${args.id} does not exist`);
1383
+ }
1384
+ return this.createErrorResponse(`Failed to fetch post ${args.id}: ${error.response?.status} ${error.response?.statusText} - ${error.response?.data?.message || error.message}`, 'FETCH_POST_ERROR', 'API_ERROR', `HTTP ${error.response?.status}: ${error.response?.statusText}`);
1385
+ }
1386
+ }
1387
+ async getPage(args) {
1388
+ const authCheck = this.ensureAuthenticated();
1389
+ if (authCheck)
1390
+ return authCheck;
1391
+ try {
1392
+ console.error(`Fetching page with ID: ${args.id}`);
1393
+ const response = await this.axiosInstance.get(`pages/${args.id}`, {
1394
+ params: { context: 'edit' }
1395
+ });
1396
+ return this.createSuccessResponse(response.data, `Successfully retrieved page ${args.id}`);
1397
+ }
1398
+ catch (error) {
1399
+ console.error(`Error fetching page ${args.id}: ${error.response?.status} - ${error.response?.statusText}`);
1400
+ console.error(`URL: ${error.config?.url}`);
1401
+ console.error(`Headers: ${JSON.stringify(error.config?.headers)}`);
1402
+ if (error.response?.status === 404) {
1403
+ return this.createErrorResponse(`Page with ID ${args.id} not found. The page may have been deleted, be in trash, or may not exist.`, 'PAGE_NOT_FOUND', 'NOT_FOUND_ERROR', `HTTP 404: Page ${args.id} does not exist`);
1404
+ }
1405
+ return this.createErrorResponse(`Failed to fetch page ${args.id}: ${error.response?.status} ${error.response?.statusText} - ${error.response?.data?.message || error.message}`, 'FETCH_PAGE_ERROR', 'API_ERROR', `HTTP ${error.response?.status}: ${error.response?.statusText}`);
1406
+ }
1407
+ }
1408
+ async createPost(args) {
1409
+ const authCheck = this.ensureAuthenticated();
1410
+ if (authCheck)
1411
+ return authCheck;
1412
+ const postData = {
1413
+ title: args.title,
1414
+ content: args.content,
1415
+ status: args.status || 'draft',
1416
+ ...(args.excerpt && { excerpt: args.excerpt }),
1417
+ };
1418
+ try {
1419
+ console.error(`Creating post with title: "${args.title}"`);
1420
+ const response = await this.axiosInstance.post('posts', postData);
1421
+ // Clear Elementor cache after creating post
1422
+ await this.clearElementorCache(response.data.id);
1423
+ return this.createSuccessResponse({
1424
+ post: response.data,
1425
+ cache_cleared: true
1426
+ }, `Post created successfully! ID: ${response.data.id}, Status: ${response.data.status}`);
1427
+ }
1428
+ catch (error) {
1429
+ console.error(`Error creating post: ${error.response?.status} - ${error.response?.statusText}`);
1430
+ console.error(`URL: ${error.config?.url}`);
1431
+ console.error(`Headers: ${JSON.stringify(error.config?.headers)}`);
1432
+ return this.createErrorResponse(`Failed to create post: ${error.response?.status} ${error.response?.statusText} - ${error.response?.data?.message || error.message}`, 'CREATE_POST_ERROR', 'API_ERROR', `HTTP ${error.response?.status}: ${error.response?.statusText}`);
1433
+ }
1434
+ }
1435
+ async updatePost(args) {
1436
+ const authCheck = this.ensureAuthenticated();
1437
+ if (authCheck)
1438
+ return authCheck;
1439
+ const updateData = {};
1440
+ if (args.title)
1441
+ updateData.title = args.title;
1442
+ if (args.content)
1443
+ updateData.content = args.content;
1444
+ if (args.status)
1445
+ updateData.status = args.status;
1446
+ if (args.excerpt)
1447
+ updateData.excerpt = args.excerpt;
1448
+ try {
1449
+ console.error(`Updating post ID: ${args.id}`);
1450
+ const response = await this.axiosInstance.post(`posts/${args.id}`, updateData);
1451
+ // Clear Elementor cache after updating post
1452
+ await this.clearElementorCache(args.id);
1453
+ return this.createSuccessResponse({
1454
+ post: response.data,
1455
+ cache_cleared: true
1456
+ }, `Post updated successfully! ID: ${response.data.id}, Status: ${response.data.status}`);
1457
+ }
1458
+ catch (error) {
1459
+ console.error(`Error updating post ${args.id}: ${error.response?.status} - ${error.response?.statusText}`);
1460
+ console.error(`URL: ${error.config?.url}`);
1461
+ console.error(`Headers: ${JSON.stringify(error.config?.headers)}`);
1462
+ if (error.response?.status === 404) {
1463
+ return this.createErrorResponse(`Post with ID ${args.id} not found. The post may have been deleted, be in trash, or may not exist.`, 'POST_NOT_FOUND', 'NOT_FOUND_ERROR', `HTTP 404: Post ${args.id} does not exist`);
1464
+ }
1465
+ return this.createErrorResponse(`Failed to update post ${args.id}: ${error.response?.status} ${error.response?.statusText} - ${error.response?.data?.message || error.message}`, 'UPDATE_POST_ERROR', 'API_ERROR', `HTTP ${error.response?.status}: ${error.response?.statusText}`);
1466
+ }
1467
+ }
1468
+ async getPages(args) {
1469
+ const authCheck = this.ensureAuthenticated();
1470
+ if (authCheck)
1471
+ return authCheck;
1472
+ const params = {
1473
+ per_page: args.per_page || 10,
1474
+ status: args.status || 'publish',
1475
+ context: 'view' // Use 'view' instead of 'edit' for lighter responses
1476
+ };
1477
+ try {
1478
+ console.error(`Fetching pages with params: ${JSON.stringify(params)}`);
1479
+ const response = await this.axiosInstance.get('pages', { params });
1480
+ // Create optimized summary objects instead of returning full content
1481
+ const pages = response.data;
1482
+ const pageSummaries = pages.map((page) => ({
1483
+ id: page.id,
1484
+ title: page.title?.rendered || '(No title)',
1485
+ status: page.status,
1486
+ date_created: page.date,
1487
+ date_modified: page.modified,
1488
+ url: page.link,
1489
+ excerpt: page.excerpt?.rendered ? page.excerpt.rendered.replace(/<[^>]*>/g, '').substring(0, 100) + '...' : '',
1490
+ author: page.author,
1491
+ parent: page.parent,
1492
+ menu_order: page.menu_order,
1493
+ featured_media: page.featured_media,
1494
+ comment_count: page.comment_count || 0,
1495
+ // Include Elementor status for quick reference
1496
+ elementor_status: page.meta?._elementor_data ? 'full' :
1497
+ page.meta?._elementor_edit_mode === 'builder' ? 'partial' : 'none'
1498
+ }));
1499
+ let debugInfo = `Found ${pages.length} pages\n`;
1500
+ // Add summary with Elementor status counts
1501
+ const elementorStats = {
1502
+ full: pageSummaries.filter((p) => p.elementor_status === 'full').length,
1503
+ partial: pageSummaries.filter((p) => p.elementor_status === 'partial').length,
1504
+ none: pageSummaries.filter((p) => p.elementor_status === 'none').length
1505
+ };
1506
+ debugInfo += `Elementor Status: ✅ Full (${elementorStats.full}), ⚠️ Partial (${elementorStats.partial}), ❌ None (${elementorStats.none})\n`;
1507
+ debugInfo += `\n💡 Use get_page tool with specific ID for full content details\n`;
1508
+ return this.createSuccessResponse({
1509
+ pages: pageSummaries, // Return summaries instead of full objects
1510
+ summary: debugInfo,
1511
+ total_found: pages.length,
1512
+ performance_note: "Optimized response - use get_page(id) for full content details"
1513
+ }, `Successfully retrieved ${pages.length} page summaries`);
1514
+ }
1515
+ catch (error) {
1516
+ console.error(`Error fetching pages: ${error.response?.status} - ${error.response?.statusText}`);
1517
+ console.error(`URL: ${error.config?.url}`);
1518
+ console.error(`Headers: ${JSON.stringify(error.config?.headers)}`);
1519
+ return this.createErrorResponse(`Failed to fetch pages: ${error.response?.status} ${error.response?.statusText} - ${error.response?.data?.message || error.message}`, 'FETCH_PAGES_ERROR', 'API_ERROR', `HTTP ${error.response?.status}: ${error.response?.statusText}`);
1520
+ }
1521
+ }
1522
+ async listAllContent(args) {
1523
+ const authCheck = this.ensureAuthenticated();
1524
+ if (authCheck)
1525
+ return authCheck;
1526
+ try {
1527
+ const perPage = args.per_page || 50;
1528
+ const statuses = args.include_all_statuses ? ['publish', 'draft', 'private', 'trash'] : ['publish'];
1529
+ console.error(`Listing all content with per_page: ${perPage}, statuses: ${statuses.join(', ')}`);
1530
+ let allContent = [];
1531
+ // Fetch posts for each status
1532
+ for (const status of statuses) {
1533
+ try {
1534
+ const postsResponse = await this.axiosInstance.get('posts', {
1535
+ params: {
1536
+ per_page: perPage,
1537
+ status,
1538
+ context: 'edit'
1539
+ }
1540
+ });
1541
+ postsResponse.data.forEach((post) => {
1542
+ const hasElementorData = post.meta?._elementor_data;
1543
+ const hasElementorEditMode = post.meta?._elementor_edit_mode;
1544
+ let elementorStatus = 'none';
1545
+ if (hasElementorData) {
1546
+ elementorStatus = 'full';
1547
+ }
1548
+ else if (hasElementorEditMode === 'builder') {
1549
+ elementorStatus = 'partial';
1550
+ }
1551
+ allContent.push({
1552
+ id: post.id,
1553
+ title: post.title.rendered || '(No title)',
1554
+ type: 'post',
1555
+ status: post.status,
1556
+ elementor_status: elementorStatus,
1557
+ url: post.link
1558
+ });
1559
+ });
1560
+ }
1561
+ catch (error) {
1562
+ console.error(`Failed to fetch posts with status ${status}: ${error.message}`);
1563
+ }
1564
+ }
1565
+ // Fetch pages for each status
1566
+ for (const status of statuses) {
1567
+ try {
1568
+ const pagesResponse = await this.axiosInstance.get('pages', {
1569
+ params: {
1570
+ per_page: perPage,
1571
+ status,
1572
+ context: 'edit'
1573
+ }
1574
+ });
1575
+ pagesResponse.data.forEach((page) => {
1576
+ const hasElementorData = page.meta?._elementor_data;
1577
+ const hasElementorEditMode = page.meta?._elementor_edit_mode;
1578
+ let elementorStatus = 'none';
1579
+ if (hasElementorData) {
1580
+ elementorStatus = 'full';
1581
+ }
1582
+ else if (hasElementorEditMode === 'builder') {
1583
+ elementorStatus = 'partial';
1584
+ }
1585
+ allContent.push({
1586
+ id: page.id,
1587
+ title: page.title.rendered || '(No title)',
1588
+ type: 'page',
1589
+ status: page.status,
1590
+ elementor_status: elementorStatus,
1591
+ url: page.link
1592
+ });
1593
+ });
1594
+ }
1595
+ catch (error) {
1596
+ console.error(`Failed to fetch pages with status ${status}: ${error.message}`);
1597
+ }
1598
+ }
1599
+ // Sort by ID
1600
+ allContent.sort((a, b) => a.id - b.id);
1601
+ // Generate summary
1602
+ const summary = {
1603
+ total: allContent.length,
1604
+ by_type: {
1605
+ posts: allContent.filter(item => item.type === 'post').length,
1606
+ pages: allContent.filter(item => item.type === 'page').length
1607
+ },
1608
+ by_elementor_status: {
1609
+ full: allContent.filter(item => item.elementor_status === 'full').length,
1610
+ partial: allContent.filter(item => item.elementor_status === 'partial').length,
1611
+ none: allContent.filter(item => item.elementor_status === 'none').length
1612
+ },
1613
+ by_status: {}
1614
+ };
1615
+ // Count by status
1616
+ allContent.forEach(item => {
1617
+ summary.by_status[item.status] = (summary.by_status[item.status] || 0) + 1;
1618
+ });
1619
+ // Format table output
1620
+ let formattedTable = `📊 Content Summary\n`;
1621
+ formattedTable += `Total items: ${summary.total}\n`;
1622
+ formattedTable += `Posts: ${summary.by_type.posts}, Pages: ${summary.by_type.pages}\n`;
1623
+ formattedTable += `Elementor: Full (${summary.by_elementor_status.full}), Partial (${summary.by_elementor_status.partial}), None (${summary.by_elementor_status.none})\n`;
1624
+ formattedTable += `Statuses: ${Object.entries(summary.by_status).map(([status, count]) => `${status} (${count})`).join(', ')}\n\n`;
1625
+ formattedTable += `📋 Content List\n`;
1626
+ formattedTable += `${'ID'.padEnd(6)} ${'Type'.padEnd(5)} ${'Status'.padEnd(8)} ${'Elementor'.padEnd(9)} Title\n`;
1627
+ formattedTable += `${'─'.repeat(6)} ${'─'.repeat(5)} ${'─'.repeat(8)} ${'─'.repeat(9)} ${'─'.repeat(50)}\n`;
1628
+ allContent.forEach(item => {
1629
+ const elementorIcon = item.elementor_status === 'full' ? '✅' : item.elementor_status === 'partial' ? '⚠️' : '❌';
1630
+ const title = item.title.length > 45 ? item.title.substring(0, 42) + '...' : item.title;
1631
+ formattedTable += `${item.id.toString().padEnd(6)} ${item.type.padEnd(5)} ${item.status.padEnd(8)} ${(elementorIcon + ' ' + item.elementor_status).padEnd(9)} ${title}\n`;
1632
+ });
1633
+ return this.createSuccessResponse({
1634
+ summary,
1635
+ content: allContent,
1636
+ formatted_table: formattedTable
1637
+ }, `Successfully retrieved ${allContent.length} content items`);
1638
+ }
1639
+ catch (error) {
1640
+ console.error(`Error listing all content: ${error.message}`);
1641
+ return this.createErrorResponse(`Failed to list content: ${error.response?.status} ${error.response?.statusText} - ${error.response?.data?.message || error.message}`, "LIST_CONTENT_ERROR", "API_ERROR", `HTTP ${error.response?.status}: ${error.response?.statusText}`);
1642
+ }
1643
+ }
1644
+ async createPage(args) {
1645
+ const authCheck = this.ensureAuthenticated();
1646
+ if (authCheck)
1647
+ return authCheck;
1648
+ const pageData = {
1649
+ title: args.title,
1650
+ content: args.content,
1651
+ status: args.status || 'draft',
1652
+ ...(args.excerpt && { excerpt: args.excerpt }),
1653
+ ...(args.parent && { parent: args.parent }),
1654
+ };
1655
+ try {
1656
+ console.error(`Creating page with title: "${args.title}"`);
1657
+ const response = await this.axiosInstance.post('pages', pageData);
1658
+ // Clear Elementor cache after creating page
1659
+ await this.clearElementorCache(response.data.id);
1660
+ return this.createSuccessResponse({
1661
+ page: response.data,
1662
+ cache_cleared: true
1663
+ }, `Page created successfully! ID: ${response.data.id}, Status: ${response.data.status}`);
1664
+ }
1665
+ catch (error) {
1666
+ console.error(`Error creating page: ${error.response?.status} - ${error.response?.statusText}`);
1667
+ console.error(`URL: ${error.config?.url}`);
1668
+ console.error(`Headers: ${JSON.stringify(error.config?.headers)}`);
1669
+ return this.createErrorResponse(`Failed to create page: ${error.response?.status} ${error.response?.statusText} - ${error.response?.data?.message || error.message}`, 'CREATE_PAGE_ERROR', 'API_ERROR', `HTTP ${error.response?.status}: ${error.response?.statusText}`);
1670
+ }
1671
+ }
1672
+ async updatePage(args) {
1673
+ const authCheck = this.ensureAuthenticated();
1674
+ if (authCheck)
1675
+ return authCheck;
1676
+ const updateData = {};
1677
+ if (args.title)
1678
+ updateData.title = args.title;
1679
+ if (args.content)
1680
+ updateData.content = args.content;
1681
+ if (args.status)
1682
+ updateData.status = args.status;
1683
+ if (args.excerpt)
1684
+ updateData.excerpt = args.excerpt;
1685
+ if (args.parent !== undefined)
1686
+ updateData.parent = args.parent;
1687
+ try {
1688
+ console.error(`Updating page ID: ${args.id}`);
1689
+ const response = await this.axiosInstance.post(`pages/${args.id}`, updateData);
1690
+ // Clear Elementor cache after updating page
1691
+ await this.clearElementorCache(args.id);
1692
+ return this.createSuccessResponse({
1693
+ page: response.data,
1694
+ cache_cleared: true
1695
+ }, `Page updated successfully! ID: ${response.data.id}, Status: ${response.data.status}`);
1696
+ }
1697
+ catch (error) {
1698
+ console.error(`Error updating page ${args.id}: ${error.response?.status} - ${error.response?.statusText}`);
1699
+ console.error(`URL: ${error.config?.url}`);
1700
+ console.error(`Headers: ${JSON.stringify(error.config?.headers)}`);
1701
+ if (error.response?.status === 404) {
1702
+ return this.createErrorResponse(`Page with ID ${args.id} not found. The page may have been deleted, be in trash, or may not exist.`, 'PAGE_NOT_FOUND', 'NOT_FOUND', `HTTP 404: Page ID ${args.id} not accessible`);
1703
+ }
1704
+ return this.createErrorResponse(`Failed to update page ${args.id}: ${error.response?.status} ${error.response?.statusText} - ${error.response?.data?.message || error.message}`, 'UPDATE_PAGE_ERROR', 'API_ERROR', `HTTP ${error.response?.status}: ${error.response?.statusText}`);
1705
+ }
1706
+ }
1707
+ async getElementorTemplates(args) {
1708
+ const authCheck = this.ensureAuthenticated();
1709
+ if (authCheck)
1710
+ return authCheck;
1711
+ const params = {
1712
+ per_page: args.per_page || 10,
1713
+ meta_key: '_elementor_template_type',
1714
+ };
1715
+ if (args.type) {
1716
+ params.meta_value = args.type;
1717
+ }
1718
+ try {
1719
+ const response = await this.axiosInstance.get('elementor_library', { params });
1720
+ // Create optimized summary objects instead of returning full content
1721
+ const templates = response.data;
1722
+ const templateSummaries = templates.map((template) => ({
1723
+ id: template.id,
1724
+ title: template.title?.rendered || '(No title)',
1725
+ template_type: template.meta?._elementor_template_type || 'unknown',
1726
+ date_created: template.date,
1727
+ date_modified: template.modified,
1728
+ status: template.status,
1729
+ author: template.author,
1730
+ // Basic template info without heavy Elementor data
1731
+ has_elementor_data: !!template.meta?._elementor_data,
1732
+ element_count: template.meta?._elementor_data ?
1733
+ (JSON.parse(template.meta._elementor_data)?.length || 0) : 0
1734
+ }));
1735
+ return this.createSuccessResponse({
1736
+ templates: templateSummaries, // Return summaries instead of full objects
1737
+ count: templateSummaries.length,
1738
+ filter_type: args.type || 'all',
1739
+ performance_note: "Optimized response - use get_elementor_data(template_id) for full template content"
1740
+ }, `Retrieved ${templateSummaries.length} Elementor template summaries`);
1741
+ }
1742
+ catch (error) {
1743
+ if (error.response?.status === 404) {
1744
+ return this.createErrorResponse('Elementor templates endpoint not found. Make sure Elementor Pro is installed and activated.', 'TEMPLATES_NOT_FOUND', 'NOT_FOUND', 'Elementor Pro required for template access');
1745
+ }
1746
+ return this.createErrorResponse(`Failed to get Elementor templates: ${error.message}`, 'GET_TEMPLATES_ERROR', 'API_ERROR', `HTTP ${error.response?.status}: ${error.response?.statusText}`);
1747
+ }
1748
+ }
1749
+ async getElementorData(args) {
1750
+ const authCheck = this.ensureAuthenticated();
1751
+ if (authCheck)
1752
+ return authCheck;
1753
+ return this.safeApiCall(async () => {
1754
+ console.error(`Getting Elementor data for ID: ${args.post_id}`);
1755
+ // Try to get as post first, then as page if that fails
1756
+ let response;
1757
+ let postType = 'post';
1758
+ let debugInfo = '';
1759
+ try {
1760
+ console.error(`Trying to fetch as post: posts/${args.post_id}`);
1761
+ response = await this.axiosInstance.get(`posts/${args.post_id}`, {
1762
+ params: { context: 'edit' }
1763
+ });
1764
+ debugInfo += `Found as post (ID: ${args.post_id})\n`;
1765
+ }
1766
+ catch (postError) {
1767
+ console.error(`Post fetch failed: ${postError.response?.status} - ${postError.response?.statusText}`);
1768
+ if (postError.response?.status === 404) {
1769
+ // Try as page
1770
+ try {
1771
+ console.error(`Trying to fetch as page: pages/${args.post_id}`);
1772
+ response = await this.axiosInstance.get(`pages/${args.post_id}`, {
1773
+ params: { context: 'edit' }
1774
+ });
1775
+ postType = 'page';
1776
+ debugInfo += `Found as page (ID: ${args.post_id})\n`;
1777
+ }
1778
+ catch (pageError) {
1779
+ console.error(`Page fetch failed: ${pageError.response?.status} - ${pageError.response?.statusText}`);
1780
+ // Provide comprehensive error message
1781
+ const errorDetails = `
1782
+ ❌ Post/Page ID ${args.post_id} not found
1783
+
1784
+ Debug Information:
1785
+ - Tried as post: ${postError.response?.status} ${postError.response?.statusText}
1786
+ - Tried as page: ${pageError.response?.status} ${pageError.response?.statusText}
1787
+
1788
+ Suggestions:
1789
+ 1. Verify the ID exists by running get_posts or get_pages
1790
+ 2. Check if the ID might be a custom post type
1791
+ 3. Ensure the post/page is not trashed
1792
+ 4. Verify your user permissions include access to this content
1793
+ `;
1794
+ return this.createErrorResponse(errorDetails.trim(), "POST_PAGE_NOT_FOUND", "NOT_FOUND", "Post/Page ID not found in either posts or pages endpoints");
1795
+ }
1796
+ }
1797
+ else {
1798
+ throw postError;
1799
+ }
1800
+ }
1801
+ // Analyze the response for Elementor data
1802
+ const data = response.data;
1803
+ console.error(`Response received for ${postType} ${args.post_id}`);
1804
+ console.error(`Meta keys available: ${data.meta ? Object.keys(data.meta).join(', ') : 'None'}`);
1805
+ const elementorData = data.meta?._elementor_data;
1806
+ const elementorEditMode = data.meta?._elementor_edit_mode;
1807
+ const elementorVersion = data.meta?._elementor_version;
1808
+ const elementorPageSettings = data.meta?._elementor_page_settings;
1809
+ debugInfo += `Title: "${data.title.rendered}"\n`;
1810
+ debugInfo += `Status: ${data.status}\n`;
1811
+ debugInfo += `Type: ${postType}\n`;
1812
+ debugInfo += `Edit Mode: ${elementorEditMode || 'None'}\n`;
1813
+ debugInfo += `Version: ${elementorVersion || 'None'}\n`;
1814
+ debugInfo += `Has Page Settings: ${elementorPageSettings ? 'Yes' : 'No'}\n`;
1815
+ debugInfo += `Has Elementor Data: ${elementorData ? 'Yes' : 'No'}\n`;
1816
+ if (elementorData) {
1817
+ try {
1818
+ const parsedData = JSON.parse(elementorData);
1819
+ debugInfo += `Elementor Elements Count: ${Array.isArray(parsedData) ? parsedData.length : 'Not an array'}\n`;
1820
+ return this.createSuccessResponse({
1821
+ post_id: args.post_id,
1822
+ post_type: postType,
1823
+ title: data.title?.rendered || data.title?.raw || 'Unknown',
1824
+ status: data.status,
1825
+ edit_mode: elementorEditMode,
1826
+ elementor_data: parsedData,
1827
+ metadata: {
1828
+ has_page_settings: !!data.meta?._elementor_page_settings,
1829
+ has_elementor_data: true,
1830
+ elements_count: Array.isArray(parsedData) ? parsedData.length : 0,
1831
+ version: elementorVersion
1832
+ }
1833
+ }, `Successfully retrieved Elementor data for ${postType} ID ${args.post_id} with ${Array.isArray(parsedData) ? parsedData.length : 0} elements`);
1834
+ }
1835
+ catch (parseError) {
1836
+ debugInfo += `⚠️ Elementor data found but failed to parse JSON\n`;
1837
+ return this.createErrorResponse(`Failed to parse Elementor data for ${postType} ID ${args.post_id}: JSON parsing error`, 'PARSE_ERROR', 'DATA_FORMAT_ERROR', `${debugInfo}\nRaw data: ${elementorData?.substring(0, 200)}...`);
1838
+ }
1839
+ }
1840
+ else {
1841
+ // Check if this is an Elementor page without data
1842
+ if (elementorEditMode === 'builder') {
1843
+ debugInfo += `\n⚠️ This appears to be an Elementor page but has no data.\n`;
1844
+ debugInfo += `Possible reasons:\n`;
1845
+ debugInfo += `- Empty Elementor page\n`;
1846
+ debugInfo += `- Cache/synchronization issue\n`;
1847
+ debugInfo += `- Elementor data stored differently\n`;
1848
+ }
1849
+ else {
1850
+ debugInfo += `\n❌ This ${postType} does not use Elementor builder.\n`;
1851
+ }
1852
+ return this.createErrorResponse(`No Elementor data found for ${postType} ID ${args.post_id}`, 'NO_ELEMENTOR_DATA', 'DATA_NOT_FOUND', `${debugInfo}\nAvailable meta keys: ${data.meta ? Object.keys(data.meta).join(', ') : 'None'}`);
1853
+ }
1854
+ }, 'getElementorData', `post/page ID ${args.post_id}`);
1855
+ }
1856
+ async clearElementorCache(postId) {
1857
+ try {
1858
+ console.error('Attempting to clear Elementor cache...');
1859
+ // Method 1: Clear specific Elementor meta that forces regeneration
1860
+ if (postId) {
1861
+ try {
1862
+ console.error(`Clearing Elementor cache meta for post ${postId}...`);
1863
+ // Try to get current page/post
1864
+ let currentData;
1865
+ let isPage = false;
1866
+ try {
1867
+ currentData = await this.axiosInstance.get(`pages/${postId}`);
1868
+ isPage = true;
1869
+ }
1870
+ catch {
1871
+ currentData = await this.axiosInstance.get(`posts/${postId}`);
1872
+ }
1873
+ // Update meta to force Elementor cache regeneration
1874
+ const cacheBreakMeta = {
1875
+ meta: {
1876
+ _elementor_css: '', // Clear generated CSS cache
1877
+ _elementor_page_settings: currentData.data.meta._elementor_page_settings || '',
1878
+ _elementor_edit_mode: 'builder',
1879
+ _elementor_version: '3.0.0', // Force version update
1880
+ _elementor_cache_bust: Date.now().toString()
1881
+ }
1882
+ };
1883
+ if (isPage) {
1884
+ await this.axiosInstance.post(`pages/${postId}`, cacheBreakMeta);
1885
+ }
1886
+ else {
1887
+ await this.axiosInstance.post(`posts/${postId}`, cacheBreakMeta);
1888
+ }
1889
+ console.error(`Elementor cache meta cleared for post ${postId}`);
1890
+ }
1891
+ catch (error) {
1892
+ console.error(`Failed to clear post-specific cache: ${error.message}`);
1893
+ }
1894
+ }
1895
+ // Method 2: Try WordPress cache clearing endpoints
1896
+ const cacheEndpoints = [
1897
+ 'wp/v2/elementor/flush',
1898
+ 'wp/v2/settings', // Sometimes triggers cache clear
1899
+ 'elementor/v1/flush-css',
1900
+ 'elementor/v1/clear-cache'
1901
+ ];
1902
+ for (const endpoint of cacheEndpoints) {
1903
+ try {
1904
+ console.error(`Trying cache clear endpoint: ${endpoint}`);
1905
+ if (endpoint === 'wp/v2/settings') {
1906
+ // Force settings update to trigger cache clear
1907
+ await this.axiosInstance.post(endpoint, {
1908
+ elementor_cache_time: Date.now().toString()
1909
+ });
1910
+ }
1911
+ else {
1912
+ await this.axiosInstance.post(endpoint, {});
1913
+ }
1914
+ console.error(`Cache clear attempted via ${endpoint}`);
1915
+ }
1916
+ catch (error) {
1917
+ console.error(`Cache clear failed via ${endpoint}: ${error.response?.status || error.message}`);
1918
+ }
1919
+ }
1920
+ // Method 3: Update Elementor global settings to force regeneration
1921
+ try {
1922
+ console.error('Forcing Elementor regeneration via options...');
1923
+ // Update multiple Elementor options that can trigger cache clear
1924
+ const optionUpdates = [
1925
+ { elementor_css_print_method: 'internal' },
1926
+ { elementor_cpt_support: ['page', 'post'] },
1927
+ { elementor_disable_color_schemes: '' },
1928
+ { elementor_disable_typography_schemes: '' },
1929
+ { elementor_cache_files_time: Date.now().toString() }
1930
+ ];
1931
+ for (const option of optionUpdates) {
1932
+ try {
1933
+ await this.axiosInstance.post('wp/v2/options', option);
1934
+ }
1935
+ catch (error) {
1936
+ console.error(`Option update failed: ${error.message}`);
1937
+ }
1938
+ }
1939
+ console.error('Elementor options updated to force regeneration');
1940
+ }
1941
+ catch (error) {
1942
+ console.error(`Failed to update Elementor options: ${error.message}`);
1943
+ }
1944
+ // Method 4: Force Elementor to rebuild by clearing ALL cache-related meta
1945
+ if (postId) {
1946
+ try {
1947
+ console.error('Forcing complete Elementor rebuild...');
1948
+ // Get current data again
1949
+ let currentData;
1950
+ let isPage = false;
1951
+ try {
1952
+ currentData = await this.axiosInstance.get(`pages/${postId}`);
1953
+ isPage = true;
1954
+ }
1955
+ catch {
1956
+ currentData = await this.axiosInstance.get(`posts/${postId}`);
1957
+ }
1958
+ // Force complete rebuild by clearing all Elementor cache meta
1959
+ const forceRebuildMeta = {
1960
+ meta: {
1961
+ _elementor_css: '',
1962
+ _elementor_page_assets: '',
1963
+ _elementor_controls_usage: '',
1964
+ _elementor_css_file: '',
1965
+ _elementor_inline_css: '',
1966
+ _elementor_template_type: '',
1967
+ _elementor_edit_mode: 'builder',
1968
+ _elementor_version: '3.20.0',
1969
+ _elementor_pro_version: '3.20.0',
1970
+ _elementor_data: currentData.data.meta._elementor_data, // Keep the data but force rebuild
1971
+ _elementor_cache_bust: Date.now().toString(),
1972
+ _elementor_force_rebuild: 'yes'
1973
+ }
1974
+ };
1975
+ if (isPage) {
1976
+ await this.axiosInstance.post(`pages/${postId}`, forceRebuildMeta);
1977
+ }
1978
+ else {
1979
+ await this.axiosInstance.post(`posts/${postId}`, forceRebuildMeta);
1980
+ }
1981
+ console.error('Complete Elementor rebuild forced');
1982
+ }
1983
+ catch (error) {
1984
+ console.error(`Force rebuild failed: ${error.message}`);
1985
+ }
1986
+ }
1987
+ // Method 5: Try to trigger WordPress object cache flush
1988
+ try {
1989
+ console.error('Attempting WordPress object cache flush...');
1990
+ await this.axiosInstance.post('wp/v2/posts', {
1991
+ title: `Cache Flush Trigger ${Date.now()}`,
1992
+ content: 'Cache flush trigger',
1993
+ status: 'draft'
1994
+ });
1995
+ console.error('Cache flush trigger post created');
1996
+ }
1997
+ catch (error) {
1998
+ console.error(`Cache flush trigger failed: ${error.message}`);
1999
+ }
2000
+ // Method 6: Try to force WordPress transient cache clear
2001
+ try {
2002
+ console.error('Attempting transient cache clear...');
2003
+ await this.axiosInstance.post('wp/v2/options', {
2004
+ elementor_transient_clear: Date.now().toString()
2005
+ });
2006
+ console.error('Transient cache clear attempted');
2007
+ }
2008
+ catch (error) {
2009
+ console.error(`Transient cache clear failed: ${error.message}`);
2010
+ }
2011
+ console.error('Aggressive Elementor cache clearing sequence completed');
2012
+ }
2013
+ catch (error) {
2014
+ console.error('Cache clearing error:', error.message);
2015
+ }
2016
+ }
2017
+ async updateElementorData(args) {
2018
+ const authCheck = this.ensureAuthenticated();
2019
+ if (authCheck)
2020
+ return authCheck;
2021
+ try {
2022
+ // Update the post meta with Elementor data
2023
+ const updateData = {
2024
+ meta: {
2025
+ _elementor_data: args.elementor_data,
2026
+ _elementor_edit_mode: 'builder',
2027
+ },
2028
+ };
2029
+ // Try to update as post first, then as page if that fails
2030
+ let response;
2031
+ let postType = 'post';
2032
+ try {
2033
+ response = await this.axiosInstance.post(`posts/${args.post_id}`, updateData);
2034
+ }
2035
+ catch (postError) {
2036
+ if (postError.response?.status === 404) {
2037
+ // Try as page
2038
+ try {
2039
+ response = await this.axiosInstance.post(`pages/${args.post_id}`, updateData);
2040
+ postType = 'page';
2041
+ }
2042
+ catch (pageError) {
2043
+ return this.createErrorResponse(`Post/Page ID ${args.post_id} not found in posts or pages`, 'POST_PAGE_NOT_FOUND', 'NOT_FOUND', 'Failed to update both post and page endpoints');
2044
+ }
2045
+ }
2046
+ else {
2047
+ return this.createErrorResponse(`Failed to update post: ${postError.response?.data?.message || postError.message}`, 'UPDATE_POST_ERROR', 'API_ERROR', `HTTP ${postError.response?.status}: ${postError.response?.statusText}`);
2048
+ }
2049
+ }
2050
+ // Clear Elementor cache after updating data
2051
+ await this.clearElementorCache(args.post_id);
2052
+ return this.createSuccessResponse({
2053
+ post_type: postType,
2054
+ post_id: args.post_id,
2055
+ cache_cleared: true,
2056
+ updated_data: true
2057
+ }, `Elementor data updated successfully for ${postType} ID: ${args.post_id}`);
2058
+ }
2059
+ catch (error) {
2060
+ return this.createErrorResponse(`Failed to update Elementor data: ${error.response?.data?.message || error.message}`, 'UPDATE_ELEMENTOR_DATA_ERROR', 'API_ERROR', `HTTP ${error.response?.status}: ${error.response?.statusText}`);
2061
+ }
2062
+ }
2063
+ async updateElementorWidget(args) {
2064
+ const authCheck = this.ensureAuthenticated();
2065
+ if (authCheck)
2066
+ return authCheck;
2067
+ try {
2068
+ // Get current Elementor data using safe parsing utility
2069
+ const parsedResult = await this.safeGetElementorData(args.post_id);
2070
+ if (!parsedResult.success || !parsedResult.data) {
2071
+ return this.createErrorResponse(`Failed to get Elementor data for post/page ID ${args.post_id}: ${parsedResult.error || 'Unknown error'}`, 'ELEMENTOR_DATA_ERROR', 'DATA_ERROR', 'Could not retrieve or parse Elementor data for widget update');
2072
+ }
2073
+ const elementorData = parsedResult.data;
2074
+ // Function to recursively find and update widget
2075
+ const updateWidgetRecursive = (elements) => {
2076
+ for (let i = 0; i < elements.length; i++) {
2077
+ const element = elements[i];
2078
+ if (element.id === args.widget_id) {
2079
+ // Found the widget, update it
2080
+ if (args.widget_settings) {
2081
+ element.settings = { ...element.settings, ...args.widget_settings };
2082
+ }
2083
+ // Special handling for HTML widget content
2084
+ if (args.widget_content && element.widgetType === 'html') {
2085
+ element.settings.html = args.widget_content;
2086
+ }
2087
+ // Special handling for text widget content
2088
+ if (args.widget_content && element.widgetType === 'text-editor') {
2089
+ element.settings.editor = args.widget_content;
2090
+ }
2091
+ // Special handling for heading widget content
2092
+ if (args.widget_content && element.widgetType === 'heading') {
2093
+ element.settings.title = args.widget_content;
2094
+ }
2095
+ return true;
2096
+ }
2097
+ // Recursively search in nested elements
2098
+ if (element.elements && element.elements.length > 0) {
2099
+ if (updateWidgetRecursive(element.elements)) {
2100
+ return true;
2101
+ }
2102
+ }
2103
+ }
2104
+ return false;
2105
+ };
2106
+ // Find and update the widget
2107
+ const widgetFound = updateWidgetRecursive(elementorData);
2108
+ if (!widgetFound) {
2109
+ return this.createErrorResponse(`Widget ID ${args.widget_id} not found in Elementor data`, 'WIDGET_NOT_FOUND', 'NOT_FOUND', `Could not locate widget with ID ${args.widget_id} in the page structure`);
2110
+ }
2111
+ // Update the page with modified data
2112
+ const updateData = {
2113
+ meta: {
2114
+ _elementor_data: JSON.stringify(elementorData),
2115
+ _elementor_edit_mode: 'builder',
2116
+ },
2117
+ };
2118
+ // Try to update as post first, then as page if that fails
2119
+ let response;
2120
+ let postType = 'post';
2121
+ try {
2122
+ response = await this.axiosInstance.post(`posts/${args.post_id}`, updateData);
2123
+ }
2124
+ catch (postError) {
2125
+ if (postError.response?.status === 404) {
2126
+ // Try as page
2127
+ try {
2128
+ response = await this.axiosInstance.post(`pages/${args.post_id}`, updateData);
2129
+ postType = 'page';
2130
+ }
2131
+ catch (pageError) {
2132
+ return this.createErrorResponse(`Post/Page ID ${args.post_id} not found in posts or pages`, 'POST_PAGE_NOT_FOUND', 'NOT_FOUND', 'Failed to update both post and page endpoints for widget update');
2133
+ }
2134
+ }
2135
+ else {
2136
+ return this.createErrorResponse(`Failed to update post: ${postError.response?.data?.message || postError.message}`, 'UPDATE_POST_ERROR', 'API_ERROR', `HTTP ${postError.response?.status}: ${postError.response?.statusText}`);
2137
+ }
2138
+ }
2139
+ // Clear Elementor cache after updating data
2140
+ await this.clearElementorCache(args.post_id);
2141
+ return this.createSuccessResponse({
2142
+ widget_id: args.widget_id,
2143
+ post_type: postType,
2144
+ post_id: args.post_id,
2145
+ cache_cleared: true,
2146
+ updated_settings: !!args.widget_settings,
2147
+ updated_content: !!args.widget_content
2148
+ }, `Elementor widget ${args.widget_id} updated successfully for ${postType} ID: ${args.post_id}`);
2149
+ }
2150
+ catch (error) {
2151
+ return this.createErrorResponse(`Failed to update Elementor widget: ${error.response?.data?.message || error.message}`, 'UPDATE_WIDGET_ERROR', 'API_ERROR', `HTTP ${error.response?.status}: ${error.response?.statusText}`);
2152
+ }
2153
+ }
2154
+ async getElementorWidget(args) {
2155
+ const authCheck = this.ensureAuthenticated();
2156
+ if (authCheck)
2157
+ return authCheck;
2158
+ try {
2159
+ console.error(`🔍 Getting widget ${args.widget_id} from post ID: ${args.post_id}`);
2160
+ // Get current Elementor data using safe parsing utility
2161
+ console.error(`🔍 Getting Elementor data for widget search in post ID: ${args.post_id}`);
2162
+ const parsedResult = await this.safeGetElementorData(args.post_id);
2163
+ if (!parsedResult.success || !parsedResult.data) {
2164
+ console.error(`❌ Failed to get Elementor data: ${parsedResult.error}`);
2165
+ return this.createErrorResponse(`Failed to retrieve Elementor data for post/page ID ${args.post_id}: ${parsedResult.error || 'Unknown error'}`, 'ELEMENTOR_DATA_ERROR', 'DATA_ERROR', 'Could not retrieve or parse Elementor data for widget retrieval');
2166
+ }
2167
+ const elementorData = parsedResult.data;
2168
+ console.error(`✅ Successfully parsed data, searching through ${elementorData.length} top-level elements for widget ${args.widget_id}`);
2169
+ // Function to recursively find widget
2170
+ const findWidgetRecursive = (elements) => {
2171
+ for (const element of elements) {
2172
+ if (element.id === args.widget_id) {
2173
+ return element;
2174
+ }
2175
+ // Recursively search in nested elements
2176
+ if (element.elements && element.elements.length > 0) {
2177
+ const found = findWidgetRecursive(element.elements);
2178
+ if (found)
2179
+ return found;
2180
+ }
2181
+ }
2182
+ return null;
2183
+ };
2184
+ // Find the widget
2185
+ const widget = findWidgetRecursive(elementorData);
2186
+ if (!widget) {
2187
+ return this.createErrorResponse(`Widget ID ${args.widget_id} not found in Elementor data`, 'WIDGET_NOT_FOUND', 'NOT_FOUND', `Could not locate widget with ID ${args.widget_id} in the page structure`);
2188
+ }
2189
+ return this.createSuccessResponse({
2190
+ widget: widget,
2191
+ widget_id: args.widget_id,
2192
+ post_id: args.post_id
2193
+ }, `Widget ${args.widget_id} retrieved successfully`);
2194
+ }
2195
+ catch (error) {
2196
+ return this.createErrorResponse(`Failed to get Elementor widget: ${error.response?.data?.message || error.message}`, 'GET_WIDGET_ERROR', 'API_ERROR', `HTTP ${error.response?.status}: ${error.response?.statusText}`);
2197
+ }
2198
+ }
2199
+ async getElementorElements(args) {
2200
+ const authCheck = this.ensureAuthenticated();
2201
+ if (authCheck)
2202
+ return authCheck;
2203
+ try {
2204
+ console.error(`🔍 Getting Elementor elements for ID: ${args.post_id}`);
2205
+ // Get current Elementor data using safe parsing utility
2206
+ const parsedResult = await this.safeGetElementorData(args.post_id);
2207
+ if (!parsedResult.success || !parsedResult.data) {
2208
+ return this.createErrorResponse(`No Elementor data found for post/page ID ${args.post_id}`, 'NO_ELEMENTOR_DATA', 'DATA_NOT_FOUND', `Post/page does not contain Elementor data or is not built with Elementor: ${parsedResult.error}`);
2209
+ }
2210
+ const elementorData = parsedResult.data;
2211
+ console.error(`✅ Successfully parsed ${elementorData.length} top-level elements`);
2212
+ // Function to recursively extract elements
2213
+ const extractElementsRecursive = (elements, level = 0) => {
2214
+ const result = [];
2215
+ for (const element of elements) {
2216
+ const elementInfo = {
2217
+ id: element.id,
2218
+ type: element.elType,
2219
+ level: level,
2220
+ };
2221
+ if (element.widgetType) {
2222
+ elementInfo.widgetType = element.widgetType;
2223
+ }
2224
+ if (args.include_content && element.settings) {
2225
+ // Include preview of content for common widget types
2226
+ if (element.widgetType === 'html' && element.settings.html) {
2227
+ elementInfo.contentPreview = element.settings.html.substring(0, 100) + (element.settings.html.length > 100 ? '...' : '');
2228
+ }
2229
+ else if (element.widgetType === 'text-editor' && element.settings.editor) {
2230
+ elementInfo.contentPreview = element.settings.editor.substring(0, 100) + (element.settings.editor.length > 100 ? '...' : '');
2231
+ }
2232
+ else if (element.widgetType === 'heading' && element.settings.title) {
2233
+ elementInfo.contentPreview = element.settings.title.substring(0, 100) + (element.settings.title.length > 100 ? '...' : '');
2234
+ }
2235
+ }
2236
+ result.push(elementInfo);
2237
+ // Recursively process nested elements
2238
+ if (element.elements && element.elements.length > 0) {
2239
+ result.push(...extractElementsRecursive(element.elements, level + 1));
2240
+ }
2241
+ }
2242
+ return result;
2243
+ };
2244
+ const elements = extractElementsRecursive(elementorData);
2245
+ return this.createSuccessResponse({
2246
+ post_id: args.post_id,
2247
+ total_elements: elements.length,
2248
+ elements: elements
2249
+ }, `Retrieved ${elements.length} Elementor elements from post/page ${args.post_id}`);
2250
+ }
2251
+ catch (error) {
2252
+ return this.createErrorResponse(`Failed to get Elementor elements: ${error.response?.data?.message || error.message}`, 'GET_ELEMENTS_ERROR', 'API_ERROR', `HTTP ${error.response?.status}: ${error.response?.statusText}`);
2253
+ }
2254
+ }
2255
+ async updateElementorSection(args) {
2256
+ const authCheck = this.ensureAuthenticated();
2257
+ if (authCheck)
2258
+ return authCheck;
2259
+ try {
2260
+ // Get current Elementor data using safe parsing utility
2261
+ const parsedResult = await this.safeGetElementorData(args.post_id);
2262
+ if (!parsedResult.success || !parsedResult.data) {
2263
+ return this.createErrorResponse(`Failed to get Elementor data for post/page ID ${args.post_id}: ${parsedResult.error || 'Unknown error'}`, 'ELEMENTOR_DATA_ERROR', 'DATA_ERROR', 'Could not retrieve or parse Elementor data for section update');
2264
+ }
2265
+ const elementorData = parsedResult.data;
2266
+ let sectionFound = false;
2267
+ let updatedWidgets = [];
2268
+ // Function to recursively find section and update widgets
2269
+ const updateSectionWidgets = (elements) => {
2270
+ for (let i = 0; i < elements.length; i++) {
2271
+ const element = elements[i];
2272
+ if (element.id === args.section_id) {
2273
+ sectionFound = true;
2274
+ // Found the section, now update widgets within it
2275
+ for (const widgetUpdate of args.widgets_updates) {
2276
+ const updated = updateWidgetInElements(element.elements || [], widgetUpdate);
2277
+ if (updated) {
2278
+ updatedWidgets.push(widgetUpdate.widget_id);
2279
+ }
2280
+ }
2281
+ return true;
2282
+ }
2283
+ // Recursively search in nested elements
2284
+ if (element.elements && element.elements.length > 0) {
2285
+ if (updateSectionWidgets(element.elements)) {
2286
+ return true;
2287
+ }
2288
+ }
2289
+ }
2290
+ return false;
2291
+ };
2292
+ // Helper function to update widget in elements array
2293
+ const updateWidgetInElements = (elements, widgetUpdate) => {
2294
+ for (const element of elements) {
2295
+ if (element.id === widgetUpdate.widget_id) {
2296
+ // Found the widget, update it
2297
+ if (widgetUpdate.widget_settings) {
2298
+ element.settings = { ...element.settings, ...widgetUpdate.widget_settings };
2299
+ }
2300
+ // Special handling for HTML widget content
2301
+ if (widgetUpdate.widget_content && element.widgetType === 'html') {
2302
+ element.settings.html = widgetUpdate.widget_content;
2303
+ }
2304
+ // Special handling for text widget content
2305
+ if (widgetUpdate.widget_content && element.widgetType === 'text-editor') {
2306
+ element.settings.editor = widgetUpdate.widget_content;
2307
+ }
2308
+ // Special handling for heading widget content
2309
+ if (widgetUpdate.widget_content && element.widgetType === 'heading') {
2310
+ element.settings.title = widgetUpdate.widget_content;
2311
+ }
2312
+ return true;
2313
+ }
2314
+ // Recursively search in nested elements
2315
+ if (element.elements && element.elements.length > 0) {
2316
+ if (updateWidgetInElements(element.elements, widgetUpdate)) {
2317
+ return true;
2318
+ }
2319
+ }
2320
+ }
2321
+ return false;
2322
+ };
2323
+ // Find section and update widgets
2324
+ updateSectionWidgets(elementorData);
2325
+ if (!sectionFound) {
2326
+ return this.createErrorResponse(`Section ID ${args.section_id} not found in Elementor data`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
2327
+ }
2328
+ // Update the page with modified data
2329
+ const updateData = {
2330
+ meta: {
2331
+ _elementor_data: JSON.stringify(elementorData),
2332
+ _elementor_edit_mode: 'builder',
2333
+ },
2334
+ };
2335
+ // Try to update as post first, then as page if that fails
2336
+ let response;
2337
+ let postType = 'post';
2338
+ try {
2339
+ response = await this.axiosInstance.post(`posts/${args.post_id}`, updateData);
2340
+ }
2341
+ catch (postError) {
2342
+ if (postError.response?.status === 404) {
2343
+ // Try as page
2344
+ try {
2345
+ response = await this.axiosInstance.post(`pages/${args.post_id}`, updateData);
2346
+ postType = 'page';
2347
+ }
2348
+ catch (pageError) {
2349
+ return this.createErrorResponse(`Post/Page ID ${args.post_id} not found in posts or pages`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
2350
+ }
2351
+ }
2352
+ else {
2353
+ throw postError;
2354
+ }
2355
+ }
2356
+ // Clear Elementor cache after updating data
2357
+ await this.clearElementorCache(args.post_id);
2358
+ return this.createSuccessResponse({
2359
+ operation_type: "update_section_widgets",
2360
+ section_id: args.section_id,
2361
+ post_id: args.post_id,
2362
+ post_type: postType,
2363
+ updated_widgets: updatedWidgets,
2364
+ widgets_not_found: args.widgets_updates.filter(w => !updatedWidgets.includes(w.widget_id)).map(w => w.widget_id),
2365
+ total_updates_requested: args.widgets_updates.length,
2366
+ successful_updates: updatedWidgets.length,
2367
+ cache_cleared: true,
2368
+ manual_cache_clearing_required: true
2369
+ }, `Elementor section ${args.section_id} updated successfully with ${updatedWidgets.length}/${args.widgets_updates.length} widget updates`);
2370
+ }
2371
+ catch (error) {
2372
+ if (error instanceof McpError) {
2373
+ throw error;
2374
+ }
2375
+ return this.createErrorResponse(`Failed to update Elementor section: ${error.response?.data?.message || error.message}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
2376
+ }
2377
+ }
2378
+ async getElementorDataDeepChunked(args) {
2379
+ const authCheck = this.ensureAuthenticated();
2380
+ if (authCheck)
2381
+ return authCheck;
2382
+ try {
2383
+ console.error(`🔍 Getting deep chunked Elementor data for ID: ${args.post_id}, element index: ${args.element_index || 0}`);
2384
+ // Get current Elementor data using safe parsing utility
2385
+ const parsedResult = await this.safeGetElementorData(args.post_id);
2386
+ if (!parsedResult.success || !parsedResult.data) {
2387
+ return this.createErrorResponse(`Failed to get Elementor data for post/page ID ${args.post_id}: ${parsedResult.error || 'Unknown error'}`, 'ELEMENTOR_DATA_ERROR', 'DATA_ERROR', 'Could not retrieve or parse Elementor data for deep chunking');
2388
+ }
2389
+ const elementorData = parsedResult.data;
2390
+ const maxDepth = args.max_depth || 2;
2391
+ const elementIndex = args.element_index || 0;
2392
+ const includeWidgetPreviews = args.include_widget_previews || false;
2393
+ if (elementIndex >= elementorData.length) {
2394
+ return this.createErrorResponse(`Element index ${elementIndex} is out of range. Total top-level elements: ${elementorData.length}`, 'INDEX_OUT_OF_RANGE', 'VALIDATION_ERROR', 'Requested element index exceeds available elements');
2395
+ }
2396
+ // Function to limit depth and content of elements
2397
+ const limitElementDepth = (element, currentDepth) => {
2398
+ const limited = {
2399
+ id: element.id,
2400
+ elType: element.elType,
2401
+ widgetType: element.widgetType || null,
2402
+ isInner: element.isInner || false,
2403
+ depth: currentDepth
2404
+ };
2405
+ // Add limited settings (exclude heavy content)
2406
+ if (element.settings) {
2407
+ limited.settings = {};
2408
+ // Include only lightweight settings, exclude heavy content
2409
+ const lightweightKeys = ['_column_size', '_inline_size', 'background_background', 'content_width', 'flex_direction', 'gap'];
2410
+ lightweightKeys.forEach(key => {
2411
+ if (element.settings[key] !== undefined) {
2412
+ limited.settings[key] = element.settings[key];
2413
+ }
2414
+ });
2415
+ // Add widget content preview if requested and if it's a widget
2416
+ if (includeWidgetPreviews && element.widgetType && element.settings) {
2417
+ if (element.settings.title) {
2418
+ limited.content_preview = String(element.settings.title).substring(0, 100);
2419
+ }
2420
+ else if (element.settings.editor) {
2421
+ limited.content_preview = String(element.settings.editor).replace(/<[^>]*>/g, '').substring(0, 100);
2422
+ }
2423
+ else if (element.settings.html) {
2424
+ limited.content_preview = String(element.settings.html).replace(/<[^>]*>/g, '').substring(0, 100);
2425
+ }
2426
+ }
2427
+ }
2428
+ // Recursively process children up to max depth
2429
+ if (element.elements && element.elements.length > 0 && currentDepth < maxDepth) {
2430
+ limited.elements = element.elements.map((child) => limitElementDepth(child, currentDepth + 1));
2431
+ limited.child_count = element.elements.length;
2432
+ }
2433
+ else if (element.elements && element.elements.length > 0) {
2434
+ // If we've reached max depth, just show summary
2435
+ limited.child_count = element.elements.length;
2436
+ limited.child_types = [...new Set(element.elements.map((child) => child.elType || child.widgetType))];
2437
+ }
2438
+ return limited;
2439
+ };
2440
+ const targetElement = elementorData[elementIndex];
2441
+ const limitedElement = limitElementDepth(targetElement, 0);
2442
+ // Calculate approximate token size (rough estimate: 4 chars per token)
2443
+ const estimatedTokens = Math.ceil(JSON.stringify(limitedElement).length / 4);
2444
+ return this.createSuccessResponse({
2445
+ post_id: args.post_id,
2446
+ element_index: elementIndex,
2447
+ max_depth: maxDepth,
2448
+ include_widget_previews: includeWidgetPreviews,
2449
+ total_top_level_elements: elementorData.length,
2450
+ element_data: limitedElement,
2451
+ navigation: {
2452
+ has_previous: elementIndex > 0,
2453
+ has_next: elementIndex < elementorData.length - 1,
2454
+ previous_index: elementIndex > 0 ? elementIndex - 1 : null,
2455
+ next_index: elementIndex < elementorData.length - 1 ? elementIndex + 1 : null
2456
+ },
2457
+ estimated_tokens: estimatedTokens
2458
+ }, `Retrieved deep chunked element ${elementIndex + 1} of ${elementorData.length} with max depth ${maxDepth} (estimated ${estimatedTokens} tokens)`);
2459
+ }
2460
+ catch (error) {
2461
+ return this.createErrorResponse(`Failed to get deep chunked Elementor data: ${error.message}`, 'GET_DEEP_CHUNKED_DATA_ERROR', 'API_ERROR', 'Operation failed');
2462
+ }
2463
+ }
2464
+ async getElementorStructureSummary(args) {
2465
+ const authCheck = this.ensureAuthenticated();
2466
+ if (authCheck)
2467
+ return authCheck;
2468
+ try {
2469
+ console.error(`📊 Getting structure summary for ID: ${args.post_id}`);
2470
+ // Get current Elementor data using safe parsing utility
2471
+ const parsedResult = await this.safeGetElementorData(args.post_id);
2472
+ if (!parsedResult.success || !parsedResult.data) {
2473
+ return this.createErrorResponse(`Failed to get Elementor data for post/page ID ${args.post_id}: ${parsedResult.error || 'Unknown error'}`, 'ELEMENTOR_DATA_ERROR', 'DATA_ERROR', 'Could not retrieve or parse Elementor data for structure summary');
2474
+ }
2475
+ const elementorData = parsedResult.data;
2476
+ const maxDepth = args.max_depth || 4;
2477
+ // Function to create a minimal structure summary
2478
+ const createStructureSummary = (elements, currentDepth = 0) => {
2479
+ return elements.map((element, index) => {
2480
+ const summary = {
2481
+ index: index,
2482
+ id: element.id,
2483
+ type: element.elType,
2484
+ depth: currentDepth
2485
+ };
2486
+ if (element.widgetType) {
2487
+ summary.widget_type = element.widgetType;
2488
+ }
2489
+ // Add some key properties for structure understanding
2490
+ if (element.settings) {
2491
+ if (element.settings._column_size) {
2492
+ summary.column_size = element.settings._column_size;
2493
+ }
2494
+ if (element.settings.content_width) {
2495
+ summary.content_width = element.settings.content_width;
2496
+ }
2497
+ if (element.settings.flex_direction) {
2498
+ summary.flex_direction = element.settings.flex_direction;
2499
+ }
2500
+ }
2501
+ // Count children and their types
2502
+ if (element.elements && element.elements.length > 0) {
2503
+ summary.child_count = element.elements.length;
2504
+ summary.child_types = [...new Set(element.elements.map((child) => child.elType || child.widgetType))];
2505
+ // Recursively process children up to max depth
2506
+ if (currentDepth < maxDepth) {
2507
+ summary.children = createStructureSummary(element.elements, currentDepth + 1);
2508
+ }
2509
+ }
2510
+ return summary;
2511
+ });
2512
+ };
2513
+ const structureSummary = createStructureSummary(elementorData);
2514
+ // Calculate total elements at each level
2515
+ const levelCounts = {};
2516
+ const widgetCounts = {};
2517
+ const countElements = (elements, depth = 0) => {
2518
+ levelCounts[depth] = (levelCounts[depth] || 0) + elements.length;
2519
+ elements.forEach(element => {
2520
+ if (element.widget_type) {
2521
+ widgetCounts[element.widget_type] = (widgetCounts[element.widget_type] || 0) + 1;
2522
+ }
2523
+ if (element.children) {
2524
+ countElements(element.children, depth + 1);
2525
+ }
2526
+ });
2527
+ };
2528
+ countElements(structureSummary);
2529
+ // Create human-readable summary
2530
+ let textSummary = `📄 Page Structure Summary (Post ID: ${args.post_id})\n\n`;
2531
+ textSummary += `🏗️ Top-level elements: ${elementorData.length}\n`;
2532
+ Object.entries(levelCounts).forEach(([depth, count]) => {
2533
+ textSummary += ` Level ${depth}: ${count} elements\n`;
2534
+ });
2535
+ if (Object.keys(widgetCounts).length > 0) {
2536
+ textSummary += `\n🧩 Widget types found:\n`;
2537
+ Object.entries(widgetCounts)
2538
+ .sort(([, a], [, b]) => b - a)
2539
+ .forEach(([widget, count]) => {
2540
+ textSummary += ` ${widget}: ${count}\n`;
2541
+ });
2542
+ }
2543
+ return this.createSuccessResponse({
2544
+ post_id: args.post_id,
2545
+ max_depth: maxDepth,
2546
+ structure: structureSummary,
2547
+ statistics: {
2548
+ total_top_level_elements: elementorData.length,
2549
+ level_counts: levelCounts,
2550
+ widget_counts: widgetCounts,
2551
+ total_widgets: Object.values(widgetCounts).reduce((sum, count) => sum + count, 0)
2552
+ },
2553
+ text_summary: textSummary
2554
+ }, `Structure summary generated for ${elementorData.length} top-level elements with ${Object.values(levelCounts).reduce((sum, count) => sum + count, 0)} total elements`);
2555
+ }
2556
+ catch (error) {
2557
+ return this.createErrorResponse(`Failed to get structure summary: ${error.message}`, 'GET_STRUCTURE_SUMMARY_ERROR', 'API_ERROR', 'Operation failed');
2558
+ }
2559
+ }
2560
+ async backupElementorData(args) {
2561
+ this.ensureAuthenticated();
2562
+ try {
2563
+ // Get current Elementor data using safe parsing utility
2564
+ const parsedResult = await this.safeGetElementorData(args.post_id);
2565
+ if (!parsedResult.success || !parsedResult.data) {
2566
+ return this.createErrorResponse(`Failed to get Elementor data for post/page ID ${args.post_id}: ${parsedResult.error || 'Unknown error'}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
2567
+ }
2568
+ // Create backup metadata
2569
+ const timestamp = new Date().toISOString();
2570
+ const backupName = args.backup_name || `backup_${timestamp}`;
2571
+ // Try to get the post/page to determine type
2572
+ let postInfo;
2573
+ let postType = 'post';
2574
+ try {
2575
+ postInfo = await this.axiosInstance.get(`posts/${args.post_id}`, {
2576
+ params: { context: 'edit' }
2577
+ });
2578
+ }
2579
+ catch (postError) {
2580
+ if (postError.response?.status === 404) {
2581
+ try {
2582
+ postInfo = await this.axiosInstance.get(`pages/${args.post_id}`, {
2583
+ params: { context: 'edit' }
2584
+ });
2585
+ postType = 'page';
2586
+ }
2587
+ catch (pageError) {
2588
+ return this.createErrorResponse(`Post/Page ID ${args.post_id} not found in posts or pages`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
2589
+ }
2590
+ }
2591
+ else {
2592
+ throw postError;
2593
+ }
2594
+ }
2595
+ // Store backup in post meta with unique key
2596
+ const backupKey = `_elementor_data_backup_${Date.now()}`;
2597
+ const backupMeta = {
2598
+ meta: {
2599
+ [backupKey]: JSON.stringify({
2600
+ backup_name: backupName,
2601
+ timestamp: timestamp,
2602
+ post_id: args.post_id,
2603
+ post_type: postType,
2604
+ post_title: postInfo.data.title.rendered || postInfo.data.title.raw,
2605
+ elementor_data: JSON.stringify(parsedResult.data)
2606
+ })
2607
+ }
2608
+ };
2609
+ // Save backup
2610
+ let response;
2611
+ if (postType === 'page') {
2612
+ response = await this.axiosInstance.post(`pages/${args.post_id}`, backupMeta);
2613
+ }
2614
+ else {
2615
+ response = await this.axiosInstance.post(`posts/${args.post_id}`, backupMeta);
2616
+ }
2617
+ return this.createSuccessResponse({
2618
+ operation_type: "backup_elementor_data",
2619
+ post_id: args.post_id,
2620
+ post_type: postType,
2621
+ post_title: postInfo.data.title.rendered || postInfo.data.title.raw,
2622
+ backup_name: backupName,
2623
+ backup_key: backupKey,
2624
+ timestamp: timestamp,
2625
+ backup_storage: "wordpress_meta",
2626
+ data_size: JSON.stringify(parsedResult.data).length
2627
+ }, `Elementor data backup created successfully! Backup key: ${backupKey}`);
2628
+ }
2629
+ catch (error) {
2630
+ if (error instanceof McpError) {
2631
+ throw error;
2632
+ }
2633
+ return this.createErrorResponse(`Failed to backup Elementor data: ${error.response?.data?.message || error.message}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
2634
+ }
2635
+ }
2636
+ async getMedia(args) {
2637
+ const authCheck = this.ensureAuthenticated();
2638
+ if (authCheck)
2639
+ return authCheck;
2640
+ const params = {
2641
+ per_page: args.per_page || 10,
2642
+ };
2643
+ if (args.media_type) {
2644
+ params.media_type = args.media_type;
2645
+ }
2646
+ try {
2647
+ console.error(`Fetching media with params: ${JSON.stringify(params)}`);
2648
+ const response = await this.axiosInstance.get('media', { params });
2649
+ // Create optimized summary objects instead of returning full content
2650
+ const media = response.data;
2651
+ const mediaSummaries = media.map((item) => ({
2652
+ id: item.id,
2653
+ title: item.title?.rendered || '(No title)',
2654
+ filename: item.media_details?.file || 'Unknown',
2655
+ mime_type: item.mime_type,
2656
+ file_size: item.media_details?.filesize || 0,
2657
+ width: item.media_details?.width || null,
2658
+ height: item.media_details?.height || null,
2659
+ url: item.source_url,
2660
+ date_uploaded: item.date,
2661
+ alt_text: item.alt_text || '',
2662
+ author: item.author,
2663
+ status: item.status
2664
+ }));
2665
+ return this.createSuccessResponse({
2666
+ media: mediaSummaries, // Return summaries instead of full objects
2667
+ count: mediaSummaries.length,
2668
+ filter: args.media_type || 'all',
2669
+ performance_note: "Optimized response - use get_media_item(id) for full details"
2670
+ }, `Retrieved ${mediaSummaries.length} media item summaries`);
2671
+ }
2672
+ catch (error) {
2673
+ console.error(`Error fetching media: ${error.response?.status} - ${error.response?.statusText}`);
2674
+ console.error(`URL: ${error.config?.url}`);
2675
+ console.error(`Headers: ${JSON.stringify(error.config?.headers)}`);
2676
+ return this.createErrorResponse(`Failed to fetch media: ${error.response?.status} ${error.response?.statusText} - ${error.response?.data?.message || error.message}`, 'GET_MEDIA_ERROR', 'API_ERROR', `HTTP ${error.response?.status}: ${error.response?.statusText}`);
2677
+ }
2678
+ }
2679
+ async uploadMedia(args) {
2680
+ this.ensureAuthenticated();
2681
+ try {
2682
+ const fs = await import('fs');
2683
+ const path = await import('path');
2684
+ if (!fs.existsSync(args.file_path)) {
2685
+ return this.createErrorResponse(`Failed to upload media: File not found: ${args.file_path}`, 'FILE_NOT_FOUND', 'VALIDATION_ERROR', 'The specified file path does not exist or is not accessible');
2686
+ }
2687
+ const formData = new FormData();
2688
+ const fileStream = fs.createReadStream(args.file_path);
2689
+ const fileName = path.basename(args.file_path);
2690
+ formData.append('file', fileStream, fileName);
2691
+ if (args.title) {
2692
+ formData.append('title', args.title);
2693
+ }
2694
+ if (args.alt_text) {
2695
+ formData.append('alt_text', args.alt_text);
2696
+ }
2697
+ const response = await this.axiosInstance.post('media', formData, {
2698
+ headers: {
2699
+ ...formData.getHeaders(),
2700
+ },
2701
+ });
2702
+ return this.createSuccessResponse({
2703
+ operation_type: "upload_media",
2704
+ media_id: response.data.id,
2705
+ url: response.data.source_url,
2706
+ title: response.data.title.rendered,
2707
+ file_path: args.file_path,
2708
+ file_name: fileName,
2709
+ mime_type: response.data.mime_type,
2710
+ file_size: response.data.media_details?.filesize || null,
2711
+ alt_text: args.alt_text || null,
2712
+ upload_date: response.data.date
2713
+ }, `Media uploaded successfully! Media ID: ${response.data.id} - ${response.data.title.rendered}`);
2714
+ }
2715
+ catch (error) {
2716
+ return this.createErrorResponse(`Failed to upload media: ${error.response?.data?.message || error.message}`, 'UPLOAD_FAILED', 'API_ERROR', `HTTP ${error.response?.status}: ${error.response?.statusText} - ${error.response?.data || error.message}`);
2717
+ }
2718
+ }
2719
+ // Helper methods for status-based responses
2720
+ createSuccessResponse(data, message) {
2721
+ return {
2722
+ content: [
2723
+ {
2724
+ type: 'text',
2725
+ text: JSON.stringify({
2726
+ status: "success",
2727
+ data: data,
2728
+ message: message || "Operation completed successfully"
2729
+ }, null, 2),
2730
+ },
2731
+ ],
2732
+ };
2733
+ }
2734
+ createErrorResponse(message, code, errorType, details) {
2735
+ return {
2736
+ content: [
2737
+ {
2738
+ type: 'text',
2739
+ text: JSON.stringify({
2740
+ status: "error",
2741
+ data: {
2742
+ message: message,
2743
+ code: code || "GENERAL_ERROR",
2744
+ error_type: errorType || "OPERATION_ERROR",
2745
+ details: details || message
2746
+ }
2747
+ }, null, 2),
2748
+ },
2749
+ ],
2750
+ };
2751
+ }
2752
+ // Section and Container Creation Tools
2753
+ async createElementorSection(args) {
2754
+ this.ensureAuthenticated();
2755
+ try {
2756
+ // Get current Elementor data using safe parsing utility
2757
+ const parsedResult = await this.safeGetElementorData(args.post_id);
2758
+ let elementorData = [];
2759
+ if (parsedResult.success && parsedResult.data) {
2760
+ elementorData = parsedResult.data;
2761
+ }
2762
+ // Generate unique ID for the section
2763
+ const sectionId = Math.random().toString(36).substr(2, 8);
2764
+ const columns = args.columns || 1;
2765
+ // Create column elements
2766
+ const columnElements = [];
2767
+ for (let i = 0; i < columns; i++) {
2768
+ const columnId = Math.random().toString(36).substr(2, 8);
2769
+ columnElements.push({
2770
+ id: columnId,
2771
+ elType: 'column',
2772
+ isInner: false,
2773
+ settings: {
2774
+ _column_size: Math.floor(100 / columns),
2775
+ _inline_size: null
2776
+ },
2777
+ elements: [],
2778
+ widgetType: null
2779
+ });
2780
+ }
2781
+ // Create new section
2782
+ const newSection = {
2783
+ id: sectionId,
2784
+ elType: 'section',
2785
+ isInner: false,
2786
+ settings: args.section_settings || {},
2787
+ elements: columnElements,
2788
+ widgetType: null
2789
+ };
2790
+ // Insert section at specified position or at the end
2791
+ if (args.position !== undefined && args.position >= 0 && args.position < elementorData.length) {
2792
+ elementorData.splice(args.position, 0, newSection);
2793
+ }
2794
+ else {
2795
+ elementorData.push(newSection);
2796
+ }
2797
+ // Update the page
2798
+ await this.updateElementorData({
2799
+ post_id: args.post_id,
2800
+ elementor_data: JSON.stringify(elementorData)
2801
+ });
2802
+ return this.createSuccessResponse({
2803
+ operation_type: "create_section",
2804
+ section_id: sectionId,
2805
+ columns: columns,
2806
+ position: args.position || elementorData.length - 1,
2807
+ post_id: args.post_id,
2808
+ section_settings: args.section_settings || {},
2809
+ column_ids: columnElements.map(col => col.id)
2810
+ }, `Section created successfully! Section ID: ${sectionId} with ${columns} columns at position ${args.position || 'end'}`);
2811
+ }
2812
+ catch (error) {
2813
+ if (error instanceof McpError) {
2814
+ throw error;
2815
+ }
2816
+ return this.createErrorResponse(`Failed to create section: ${error.message}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
2817
+ }
2818
+ }
2819
+ async createElementorContainer(args) {
2820
+ this.ensureAuthenticated();
2821
+ try {
2822
+ // Get current Elementor data using safe parsing utility
2823
+ const parsedResult = await this.safeGetElementorData(args.post_id);
2824
+ let elementorData = [];
2825
+ if (parsedResult.success && parsedResult.data) {
2826
+ elementorData = parsedResult.data;
2827
+ }
2828
+ // Generate unique ID for the container
2829
+ const containerId = Math.random().toString(36).substr(2, 8);
2830
+ // Create new container (Elementor v3.6+ flexbox container)
2831
+ const newContainer = {
2832
+ id: containerId,
2833
+ elType: 'container',
2834
+ isInner: false,
2835
+ settings: {
2836
+ content_width: 'boxed',
2837
+ flex_direction: 'column',
2838
+ ...args.container_settings
2839
+ },
2840
+ elements: [],
2841
+ widgetType: null
2842
+ };
2843
+ // Insert container at specified position or at the end
2844
+ if (args.position !== undefined && args.position >= 0 && args.position < elementorData.length) {
2845
+ elementorData.splice(args.position, 0, newContainer);
2846
+ }
2847
+ else {
2848
+ elementorData.push(newContainer);
2849
+ }
2850
+ // Update the page
2851
+ await this.updateElementorData({
2852
+ post_id: args.post_id,
2853
+ elementor_data: JSON.stringify(elementorData)
2854
+ });
2855
+ return this.createSuccessResponse({
2856
+ operation_type: "create_container",
2857
+ container_id: containerId,
2858
+ position: args.position || elementorData.length - 1,
2859
+ post_id: args.post_id,
2860
+ container_settings: {
2861
+ content_width: 'boxed',
2862
+ flex_direction: 'column',
2863
+ ...args.container_settings
2864
+ }
2865
+ }, `Container created successfully! Container ID: ${containerId} at position ${args.position || 'end'}`);
2866
+ }
2867
+ catch (error) {
2868
+ if (error instanceof McpError) {
2869
+ throw error;
2870
+ }
2871
+ return this.createErrorResponse(`Failed to create container: ${error.message}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
2872
+ }
2873
+ }
2874
+ async addColumnToSection(args) {
2875
+ this.ensureAuthenticated();
2876
+ try {
2877
+ // Get current Elementor data using safe parsing utility
2878
+ const parsedResult = await this.safeGetElementorData(args.post_id);
2879
+ if (!parsedResult.success || !parsedResult.data) {
2880
+ return this.createErrorResponse(`Failed to get Elementor data for post/page ID ${args.post_id}: ${parsedResult.error || 'Unknown error'}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
2881
+ }
2882
+ const elementorData = parsedResult.data;
2883
+ const columnsToAdd = args.columns_to_add || 1;
2884
+ let sectionFound = false;
2885
+ // Function to find and update section
2886
+ const findAndUpdateSection = (elements) => {
2887
+ for (let element of elements) {
2888
+ if (element.id === args.section_id && element.elType === 'section') {
2889
+ // Add new columns
2890
+ for (let i = 0; i < columnsToAdd; i++) {
2891
+ const columnId = Math.random().toString(36).substr(2, 8);
2892
+ element.elements.push({
2893
+ id: columnId,
2894
+ elType: 'column',
2895
+ isInner: false,
2896
+ settings: {},
2897
+ elements: [],
2898
+ widgetType: null
2899
+ });
2900
+ }
2901
+ return true;
2902
+ }
2903
+ if (element.elements && element.elements.length > 0) {
2904
+ if (findAndUpdateSection(element.elements)) {
2905
+ return true;
2906
+ }
2907
+ }
2908
+ }
2909
+ return false;
2910
+ };
2911
+ sectionFound = findAndUpdateSection(elementorData);
2912
+ if (!sectionFound) {
2913
+ return this.createErrorResponse(`Section ID ${args.section_id} not found`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
2914
+ }
2915
+ // Update the page
2916
+ await this.updateElementorData({
2917
+ post_id: args.post_id,
2918
+ elementor_data: JSON.stringify(elementorData)
2919
+ });
2920
+ return this.createSuccessResponse({
2921
+ operation_type: "add_columns_to_section",
2922
+ section_id: args.section_id,
2923
+ columns_added: columnsToAdd,
2924
+ post_id: args.post_id,
2925
+ new_column_ids: Array.from({ length: columnsToAdd }, () => Math.random().toString(36).substr(2, 8))
2926
+ }, `Successfully added ${columnsToAdd} column(s) to section ${args.section_id}`);
2927
+ }
2928
+ catch (error) {
2929
+ if (error instanceof McpError) {
2930
+ throw error;
2931
+ }
2932
+ return this.createErrorResponse(`Failed to add columns to section: ${error.message}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
2933
+ }
2934
+ }
2935
+ async duplicateSection(args) {
2936
+ this.ensureAuthenticated();
2937
+ try {
2938
+ // Get current Elementor data using safe parsing utility
2939
+ const parsedResult = await this.safeGetElementorData(args.post_id);
2940
+ if (!parsedResult.success || !parsedResult.data) {
2941
+ return this.createErrorResponse(`Failed to get Elementor data for post/page ID ${args.post_id}: ${parsedResult.error || 'Unknown error'}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
2942
+ }
2943
+ const elementorData = parsedResult.data;
2944
+ let sectionToDuplicate = null;
2945
+ let insertIndex = elementorData.length;
2946
+ // Find the section to duplicate
2947
+ for (let i = 0; i < elementorData.length; i++) {
2948
+ if (elementorData[i].id === args.section_id && elementorData[i].elType === 'section') {
2949
+ sectionToDuplicate = JSON.parse(JSON.stringify(elementorData[i])); // Deep copy
2950
+ insertIndex = args.position !== undefined ? args.position : i + 1;
2951
+ break;
2952
+ }
2953
+ }
2954
+ if (!sectionToDuplicate) {
2955
+ return this.createErrorResponse(`Section ID ${args.section_id} not found`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
2956
+ }
2957
+ // Generate new IDs for the duplicated section and all its children
2958
+ const generateNewIds = (element) => {
2959
+ element.id = Math.random().toString(36).substr(2, 8);
2960
+ if (element.elements) {
2961
+ element.elements.forEach(generateNewIds);
2962
+ }
2963
+ };
2964
+ generateNewIds(sectionToDuplicate);
2965
+ // Insert the duplicated section
2966
+ elementorData.splice(insertIndex, 0, sectionToDuplicate);
2967
+ // Update the page
2968
+ await this.updateElementorData({
2969
+ post_id: args.post_id,
2970
+ elementor_data: JSON.stringify(elementorData)
2971
+ });
2972
+ return this.createSuccessResponse({
2973
+ operation_type: "duplicate_section",
2974
+ original_section_id: args.section_id,
2975
+ new_section_id: sectionToDuplicate.id,
2976
+ position: insertIndex,
2977
+ post_id: args.post_id,
2978
+ duplicated_elements_count: 1 + (sectionToDuplicate.elements ? sectionToDuplicate.elements.length : 0)
2979
+ }, `Section duplicated successfully! New section ID: ${sectionToDuplicate.id} inserted at position: ${insertIndex}`);
2980
+ }
2981
+ catch (error) {
2982
+ if (error instanceof McpError) {
2983
+ throw error;
2984
+ }
2985
+ return this.createErrorResponse(`Failed to duplicate section: ${error.message}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
2986
+ }
2987
+ }
2988
+ // Widget Addition Tools
2989
+ async addWidgetToSection(args) {
2990
+ this.ensureAuthenticated();
2991
+ try {
2992
+ console.error(`➕ Adding widget ${args.widget_type} to post ID: ${args.post_id}`);
2993
+ if (args.section_id)
2994
+ console.error(`📍 Target section: ${args.section_id}`);
2995
+ if (args.column_id)
2996
+ console.error(`📍 Target column: ${args.column_id}`);
2997
+ // Get current Elementor data using safe parsing utility
2998
+ console.error(`🔄 Fetching Elementor data for widget addition...`);
2999
+ const parsedResult = await this.safeGetElementorData(args.post_id);
3000
+ if (!parsedResult.success || !parsedResult.data) {
3001
+ console.error(`❌ Failed to get Elementor data: ${parsedResult.error}`);
3002
+ return this.createErrorResponse(`Failed to get Elementor data for post/page ID ${args.post_id}: ${parsedResult.error || 'Unknown error'}. Cannot add widget.`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3003
+ }
3004
+ const elementorData = parsedResult.data;
3005
+ console.error(`✅ Successfully parsed data for widget addition: ${elementorData.length} top-level elements`);
3006
+ // Generate unique ID for the widget
3007
+ const widgetId = Math.random().toString(36).substr(2, 8);
3008
+ // Create new widget
3009
+ const newWidget = {
3010
+ id: widgetId,
3011
+ elType: 'widget',
3012
+ widgetType: args.widget_type,
3013
+ isInner: false,
3014
+ settings: args.widget_settings || {},
3015
+ elements: []
3016
+ };
3017
+ let targetFound = false;
3018
+ // Function to find target container and add widget
3019
+ const visited = new Set(); // Create visited set once, outside the function
3020
+ const findAndAddWidget = (elements) => {
3021
+ for (let element of elements) {
3022
+ // Prevent infinite recursion by tracking visited elements
3023
+ if (visited.has(element.id)) {
3024
+ continue;
3025
+ }
3026
+ visited.add(element.id);
3027
+ // If column_id is specified, look for that specific column
3028
+ if (args.column_id && element.id === args.column_id && element.elType === 'column') {
3029
+ if (args.position !== undefined && args.position >= 0 && args.position < element.elements.length) {
3030
+ element.elements.splice(args.position, 0, newWidget);
3031
+ }
3032
+ else {
3033
+ element.elements.push(newWidget);
3034
+ }
3035
+ return true;
3036
+ }
3037
+ // If section_id is specified, add to section or container
3038
+ if (args.section_id && element.id === args.section_id && (element.elType === 'section' || element.elType === 'container')) {
3039
+ if (element.elType === 'container') {
3040
+ // For containers, add widget directly
3041
+ if (args.position !== undefined && args.position >= 0 && args.position < element.elements.length) {
3042
+ element.elements.splice(args.position, 0, newWidget);
3043
+ }
3044
+ else {
3045
+ element.elements.push(newWidget);
3046
+ }
3047
+ return true;
3048
+ }
3049
+ else if (element.elType === 'section') {
3050
+ // For sections, add to first column
3051
+ if (element.elements && element.elements.length > 0) {
3052
+ const firstColumn = element.elements[0];
3053
+ if (args.position !== undefined && args.position >= 0 && args.position < firstColumn.elements.length) {
3054
+ firstColumn.elements.splice(args.position, 0, newWidget);
3055
+ }
3056
+ else {
3057
+ firstColumn.elements.push(newWidget);
3058
+ }
3059
+ return true;
3060
+ }
3061
+ }
3062
+ }
3063
+ // If no specific target, add to first available column or container
3064
+ if (!args.section_id && !args.column_id) {
3065
+ if (element.elType === 'column' || element.elType === 'container') {
3066
+ element.elements.push(newWidget);
3067
+ return true;
3068
+ }
3069
+ }
3070
+ // Recursively search
3071
+ if (element.elements && element.elements.length > 0) {
3072
+ if (findAndAddWidget(element.elements)) {
3073
+ return true;
3074
+ }
3075
+ }
3076
+ }
3077
+ return false;
3078
+ };
3079
+ targetFound = findAndAddWidget(elementorData);
3080
+ if (!targetFound) {
3081
+ return this.createErrorResponse(`Target container not found (section_id: ${args.section_id}, column_id: ${args.column_id})`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3082
+ }
3083
+ // Update the page
3084
+ await this.updateElementorData({
3085
+ post_id: args.post_id,
3086
+ elementor_data: JSON.stringify(elementorData)
3087
+ });
3088
+ return this.createSuccessResponse({
3089
+ widget_id: widgetId,
3090
+ widget_type: args.widget_type,
3091
+ post_id: args.post_id,
3092
+ target_section: args.section_id,
3093
+ target_column: args.column_id,
3094
+ position: args.position || 'end'
3095
+ }, `Widget added successfully! Widget ID: ${widgetId}, Type: ${args.widget_type}`);
3096
+ }
3097
+ catch (error) {
3098
+ if (error instanceof McpError) {
3099
+ throw error;
3100
+ }
3101
+ return this.createErrorResponse(`Failed to add widget: ${error.message}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3102
+ }
3103
+ }
3104
+ async insertWidgetAtPosition(args) {
3105
+ this.ensureAuthenticated();
3106
+ try {
3107
+ // Get current Elementor data using safe parsing utility
3108
+ const parsedResult = await this.safeGetElementorData(args.post_id);
3109
+ if (!parsedResult.success || !parsedResult.data) {
3110
+ return this.createErrorResponse(`Failed to get Elementor data for post/page ID ${args.post_id}: ${parsedResult.error || 'Unknown error'}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3111
+ }
3112
+ const elementorData = parsedResult.data;
3113
+ // Generate unique ID for the widget
3114
+ const widgetId = Math.random().toString(36).substr(2, 8);
3115
+ // Create new widget
3116
+ const newWidget = {
3117
+ id: widgetId,
3118
+ elType: 'widget',
3119
+ widgetType: args.widget_type,
3120
+ isInner: false,
3121
+ settings: args.widget_settings || {},
3122
+ elements: []
3123
+ };
3124
+ let targetFound = false;
3125
+ // Function to find target element and insert widget
3126
+ const findAndInsertWidget = (elements, parent) => {
3127
+ for (let i = 0; i < elements.length; i++) {
3128
+ const element = elements[i];
3129
+ if (element.id === args.target_element_id) {
3130
+ const insertIndex = args.insert_position === 'before' ? i : i + 1;
3131
+ parent.elements.splice(insertIndex, 0, newWidget);
3132
+ return true;
3133
+ }
3134
+ if (element.elements && element.elements.length > 0) {
3135
+ if (findAndInsertWidget(element.elements, element)) {
3136
+ return true;
3137
+ }
3138
+ }
3139
+ }
3140
+ return false;
3141
+ };
3142
+ // Create a mock parent for top-level elements
3143
+ const mockParent = { elements: elementorData };
3144
+ targetFound = findAndInsertWidget(elementorData, mockParent);
3145
+ if (!targetFound) {
3146
+ return this.createErrorResponse(`Target element ID ${args.target_element_id} not found`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3147
+ }
3148
+ // Update the page
3149
+ await this.updateElementorData({
3150
+ post_id: args.post_id,
3151
+ elementor_data: JSON.stringify(elementorData)
3152
+ });
3153
+ return this.createSuccessResponse({
3154
+ operation_type: "insert_widget_at_position",
3155
+ widget_id: widgetId,
3156
+ widget_type: args.widget_type,
3157
+ target_element_id: args.target_element_id,
3158
+ insert_position: args.insert_position || 'after',
3159
+ post_id: args.post_id,
3160
+ widget_settings: args.widget_settings || {}
3161
+ }, `Widget inserted successfully! Widget ID: ${widgetId} (${args.widget_type}) ${args.insert_position || 'after'} element: ${args.target_element_id}`);
3162
+ }
3163
+ catch (error) {
3164
+ if (error instanceof McpError) {
3165
+ throw error;
3166
+ }
3167
+ return this.createErrorResponse(`Failed to insert widget: ${error.message}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3168
+ }
3169
+ }
3170
+ async cloneWidget(args) {
3171
+ this.ensureAuthenticated();
3172
+ try {
3173
+ // Get current Elementor data using safe parsing utility
3174
+ const parsedResult = await this.safeGetElementorData(args.post_id);
3175
+ if (!parsedResult.success || !parsedResult.data) {
3176
+ return this.createErrorResponse(`Failed to get Elementor data for post/page ID ${args.post_id}: ${parsedResult.error || 'Unknown error'}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3177
+ }
3178
+ const elementorData = parsedResult.data;
3179
+ let widgetToClone = null;
3180
+ // Function to find widget to clone
3181
+ const findWidgetVisited = new Set(); // Create visited set once, outside the function
3182
+ const findWidget = (elements) => {
3183
+ for (let element of elements) {
3184
+ // Prevent infinite recursion by tracking visited elements
3185
+ if (findWidgetVisited.has(element.id)) {
3186
+ continue;
3187
+ }
3188
+ findWidgetVisited.add(element.id);
3189
+ if (element.id === args.widget_id) {
3190
+ return JSON.parse(JSON.stringify(element)); // Deep copy
3191
+ }
3192
+ if (element.elements && element.elements.length > 0) {
3193
+ const found = findWidget(element.elements);
3194
+ if (found)
3195
+ return found;
3196
+ }
3197
+ }
3198
+ return null;
3199
+ };
3200
+ widgetToClone = findWidget(elementorData);
3201
+ if (!widgetToClone) {
3202
+ return this.createErrorResponse(`Widget ID ${args.widget_id} not found`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3203
+ }
3204
+ // Generate new ID for cloned widget
3205
+ const generateNewIds = (element) => {
3206
+ element.id = Math.random().toString(36).substr(2, 8);
3207
+ if (element.elements) {
3208
+ element.elements.forEach(generateNewIds);
3209
+ }
3210
+ };
3211
+ generateNewIds(widgetToClone);
3212
+ // Insert cloned widget
3213
+ if (args.target_element_id) {
3214
+ // Insert at specific position
3215
+ let targetFound = false;
3216
+ const insertWidgetVisited = new Set(); // Create visited set once, outside the function
3217
+ const findAndInsertWidget = (elements, parent) => {
3218
+ for (let i = 0; i < elements.length; i++) {
3219
+ const element = elements[i];
3220
+ // Prevent infinite recursion by tracking visited elements
3221
+ if (insertWidgetVisited.has(element.id)) {
3222
+ continue;
3223
+ }
3224
+ insertWidgetVisited.add(element.id);
3225
+ if (element.id === args.target_element_id) {
3226
+ const insertIndex = args.insert_position === 'before' ? i : i + 1;
3227
+ parent.elements.splice(insertIndex, 0, widgetToClone);
3228
+ return true;
3229
+ }
3230
+ if (element.elements && element.elements.length > 0) {
3231
+ if (findAndInsertWidget(element.elements, element)) {
3232
+ return true;
3233
+ }
3234
+ }
3235
+ }
3236
+ return false;
3237
+ };
3238
+ const mockParent = { elements: elementorData };
3239
+ targetFound = findAndInsertWidget(elementorData, mockParent);
3240
+ if (!targetFound) {
3241
+ return this.createErrorResponse(`Target element ID ${args.target_element_id} not found`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3242
+ }
3243
+ }
3244
+ else {
3245
+ // Add to first available column or container
3246
+ const containerVisited = new Set(); // Create visited set once, outside the function
3247
+ const findFirstContainer = (elements) => {
3248
+ for (let element of elements) {
3249
+ // Prevent infinite recursion by tracking visited elements
3250
+ if (containerVisited.has(element.id)) {
3251
+ continue;
3252
+ }
3253
+ containerVisited.add(element.id);
3254
+ // Check for traditional column
3255
+ if (element.elType === 'column') {
3256
+ element.elements.push(widgetToClone);
3257
+ return true;
3258
+ }
3259
+ // Check for new container (Elementor v3.6+)
3260
+ if (element.elType === 'container') {
3261
+ element.elements.push(widgetToClone);
3262
+ return true;
3263
+ }
3264
+ if (element.elements && element.elements.length > 0) {
3265
+ if (findFirstContainer(element.elements)) {
3266
+ return true;
3267
+ }
3268
+ }
3269
+ }
3270
+ return false;
3271
+ };
3272
+ if (!findFirstContainer(elementorData)) {
3273
+ return this.createErrorResponse('No column or container found to place cloned widget', "OPERATION_ERROR", "API_ERROR", "Operation failed");
3274
+ }
3275
+ }
3276
+ // Update the page
3277
+ await this.updateElementorData({
3278
+ post_id: args.post_id,
3279
+ elementor_data: JSON.stringify(elementorData)
3280
+ });
3281
+ return this.createSuccessResponse({
3282
+ post_id: args.post_id,
3283
+ original_widget_id: args.widget_id,
3284
+ new_widget_id: widgetToClone.id,
3285
+ widget_type: widgetToClone.widgetType || null,
3286
+ target_element_id: args.target_element_id || null,
3287
+ insert_position: args.insert_position || "after",
3288
+ operation: "widget_cloning",
3289
+ cloned_settings: widgetToClone.settings || {}
3290
+ }, `Widget cloned successfully! New widget ID: ${widgetToClone.id}`);
3291
+ }
3292
+ catch (error) {
3293
+ if (error instanceof McpError) {
3294
+ throw error;
3295
+ }
3296
+ return this.createErrorResponse(`Failed to clone widget: ${error.message}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3297
+ }
3298
+ }
3299
+ async moveWidget(args) {
3300
+ this.ensureAuthenticated();
3301
+ try {
3302
+ // Get current Elementor data using safe parsing utility
3303
+ const parsedResult = await this.safeGetElementorData(args.post_id);
3304
+ if (!parsedResult.success || !parsedResult.data) {
3305
+ return this.createErrorResponse(`Failed to get Elementor data for post/page ID ${args.post_id}: ${parsedResult.error || 'Unknown error'}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3306
+ }
3307
+ const elementorData = parsedResult.data;
3308
+ let widgetToMove = null;
3309
+ let sourceLocation = { column_id: null, previous_position: -1 };
3310
+ let targetLocation = {
3311
+ column_id: args.target_column_id || null,
3312
+ new_position: args.position !== undefined ? args.position : "end"
3313
+ };
3314
+ // Function to find and remove widget
3315
+ const findAndRemoveWidget = (elements, parent = null) => {
3316
+ for (let i = 0; i < elements.length; i++) {
3317
+ const element = elements[i];
3318
+ if (element.id === args.widget_id) {
3319
+ widgetToMove = elements.splice(i, 1)[0];
3320
+ if (parent && parent.elType === 'column') {
3321
+ sourceLocation.column_id = parent.id;
3322
+ sourceLocation.previous_position = i;
3323
+ }
3324
+ return true;
3325
+ }
3326
+ if (element.elements && element.elements.length > 0) {
3327
+ if (findAndRemoveWidget(element.elements, element)) {
3328
+ return true;
3329
+ }
3330
+ }
3331
+ }
3332
+ return false;
3333
+ };
3334
+ if (!findAndRemoveWidget(elementorData)) {
3335
+ return this.createErrorResponse("Widget ID ${args.widget_id} not found", "OPERATION_ERROR", "API_ERROR", "Operation failed");
3336
+ }
3337
+ // Function to find target and add widget
3338
+ const findTargetAndAddWidget = (elements) => {
3339
+ for (let element of elements) {
3340
+ // If column_id is specified, look for that specific column
3341
+ if (args.target_column_id && element.id === args.target_column_id && element.elType === 'column') {
3342
+ targetLocation.column_id = element.id;
3343
+ if (args.position !== undefined && args.position >= 0 && args.position < element.elements.length) {
3344
+ element.elements.splice(args.position, 0, widgetToMove);
3345
+ targetLocation.new_position = args.position;
3346
+ }
3347
+ else {
3348
+ element.elements.push(widgetToMove);
3349
+ targetLocation.new_position = element.elements.length - 1;
3350
+ }
3351
+ return true;
3352
+ }
3353
+ // If section_id is specified, add to first column of that section
3354
+ if (args.target_section_id && element.id === args.target_section_id && element.elType === 'section') {
3355
+ if (element.elements && element.elements.length > 0) {
3356
+ const firstColumn = element.elements[0];
3357
+ targetLocation.column_id = firstColumn.id;
3358
+ if (args.position !== undefined && args.position >= 0 && args.position < firstColumn.elements.length) {
3359
+ firstColumn.elements.splice(args.position, 0, widgetToMove);
3360
+ targetLocation.new_position = args.position;
3361
+ }
3362
+ else {
3363
+ firstColumn.elements.push(widgetToMove);
3364
+ targetLocation.new_position = firstColumn.elements.length - 1;
3365
+ }
3366
+ return true;
3367
+ }
3368
+ }
3369
+ if (element.elements && element.elements.length > 0) {
3370
+ if (findTargetAndAddWidget(element.elements)) {
3371
+ return true;
3372
+ }
3373
+ }
3374
+ }
3375
+ return false;
3376
+ };
3377
+ if (!findTargetAndAddWidget(elementorData)) {
3378
+ return this.createErrorResponse(`Target container not found (section_id: ${args.target_section_id}, column_id: ${args.target_column_id})`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3379
+ }
3380
+ // Update the page
3381
+ await this.updateElementorData({
3382
+ post_id: args.post_id,
3383
+ elementor_data: JSON.stringify(elementorData)
3384
+ });
3385
+ return this.createSuccessResponse({
3386
+ post_id: args.post_id,
3387
+ widget_id: args.widget_id,
3388
+ widget_type: widgetToMove.widgetType || null,
3389
+ source_location: sourceLocation,
3390
+ target_location: targetLocation,
3391
+ operation: "widget_move"
3392
+ }, `Widget moved successfully! Widget ID: ${args.widget_id}`);
3393
+ }
3394
+ catch (error) {
3395
+ if (error instanceof McpError) {
3396
+ throw error;
3397
+ }
3398
+ return this.createErrorResponse(`Failed to move widget: ${error.message}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3399
+ }
3400
+ }
3401
+ // Element Management Tools
3402
+ async deleteElementorElement(args) {
3403
+ this.ensureAuthenticated();
3404
+ try {
3405
+ // Get current Elementor data using safe parsing utility
3406
+ const parsedResult = await this.safeGetElementorData(args.post_id);
3407
+ if (!parsedResult.success || !parsedResult.data) {
3408
+ return this.createErrorResponse(`Failed to get Elementor data for post/page ID ${args.post_id}: ${parsedResult.error || 'Unknown error'}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3409
+ }
3410
+ const elementorData = parsedResult.data;
3411
+ let elementDeleted = false;
3412
+ let deletedElement = null;
3413
+ let parentContainer = null;
3414
+ let remainingElementsInParent = 0;
3415
+ // Function to find and delete element
3416
+ const findAndDeleteElement = (elements, parent = null) => {
3417
+ for (let i = 0; i < elements.length; i++) {
3418
+ const element = elements[i];
3419
+ if (element.id === args.element_id) {
3420
+ deletedElement = { ...element };
3421
+ parentContainer = parent ? {
3422
+ type: parent.elType,
3423
+ id: parent.id
3424
+ } : null;
3425
+ elements.splice(i, 1);
3426
+ remainingElementsInParent = elements.length;
3427
+ return true;
3428
+ }
3429
+ if (element.elements && element.elements.length > 0) {
3430
+ if (findAndDeleteElement(element.elements, element)) {
3431
+ return true;
3432
+ }
3433
+ }
3434
+ }
3435
+ return false;
3436
+ };
3437
+ elementDeleted = findAndDeleteElement(elementorData);
3438
+ if (!elementDeleted) {
3439
+ return this.createErrorResponse(`Element ID ${args.element_id} not found`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3440
+ }
3441
+ // Update the page
3442
+ await this.updateElementorData({
3443
+ post_id: args.post_id,
3444
+ elementor_data: JSON.stringify(elementorData)
3445
+ });
3446
+ return this.createSuccessResponse({
3447
+ post_id: args.post_id,
3448
+ deleted_element_id: args.element_id,
3449
+ element_type: deletedElement.elType,
3450
+ widget_type: deletedElement.widgetType || null,
3451
+ parent_container: parentContainer,
3452
+ operation: "element_deletion",
3453
+ remaining_elements_in_parent: remainingElementsInParent
3454
+ }, `Element deleted successfully! Element ID: ${args.element_id}`);
3455
+ }
3456
+ catch (error) {
3457
+ if (error instanceof McpError) {
3458
+ throw error;
3459
+ }
3460
+ return this.createErrorResponse(`Failed to delete element: ${error.message}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3461
+ }
3462
+ }
3463
+ async reorderElements(args) {
3464
+ this.ensureAuthenticated();
3465
+ try {
3466
+ // Get current Elementor data using safe parsing utility
3467
+ const parsedResult = await this.safeGetElementorData(args.post_id);
3468
+ if (!parsedResult.success || !parsedResult.data) {
3469
+ return this.createErrorResponse("Failed to get Elementor data for post/page ID ${args.post_id}: ${parsedResult.error || 'Unknown error'}", "OPERATION_ERROR", "API_ERROR", "Operation failed");
3470
+ }
3471
+ const elementorData = parsedResult.data;
3472
+ let containerFound = false;
3473
+ let containerType = '';
3474
+ let newOrderDetails = [];
3475
+ // Function to find container and reorder elements
3476
+ const findAndReorderElements = (elements) => {
3477
+ for (let element of elements) {
3478
+ if (element.id === args.container_id) {
3479
+ containerType = element.elType;
3480
+ const oldElements = [...element.elements];
3481
+ const newElements = [];
3482
+ // Reorder according to provided array
3483
+ for (let elementId of args.element_ids) {
3484
+ const foundElement = oldElements.find(el => el.id === elementId);
3485
+ if (foundElement) {
3486
+ newElements.push(foundElement);
3487
+ }
3488
+ }
3489
+ // Add any elements that weren't in the reorder list
3490
+ for (let oldElement of oldElements) {
3491
+ if (!args.element_ids.includes(oldElement.id)) {
3492
+ newElements.push(oldElement);
3493
+ }
3494
+ }
3495
+ // Track the new order details
3496
+ newOrderDetails = newElements.map((el, index) => ({
3497
+ element_id: el.id,
3498
+ element_type: el.elType,
3499
+ widget_type: el.widgetType || null,
3500
+ position: index
3501
+ }));
3502
+ element.elements = newElements;
3503
+ return true;
3504
+ }
3505
+ if (element.elements && element.elements.length > 0) {
3506
+ if (findAndReorderElements(element.elements)) {
3507
+ return true;
3508
+ }
3509
+ }
3510
+ }
3511
+ return false;
3512
+ };
3513
+ containerFound = findAndReorderElements(elementorData);
3514
+ if (!containerFound) {
3515
+ return this.createErrorResponse(`Container ID ${args.container_id} not found`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3516
+ }
3517
+ // Update the page
3518
+ await this.updateElementorData({
3519
+ post_id: args.post_id,
3520
+ elementor_data: JSON.stringify(elementorData)
3521
+ });
3522
+ return this.createSuccessResponse({
3523
+ post_id: args.post_id,
3524
+ container_id: args.container_id,
3525
+ container_type: containerType,
3526
+ elements_reordered: newOrderDetails.length,
3527
+ new_order: newOrderDetails,
3528
+ operation: "element_reordering"
3529
+ }, `Elements reordered successfully in container: ${args.container_id}`);
3530
+ }
3531
+ catch (error) {
3532
+ if (error instanceof McpError) {
3533
+ throw error;
3534
+ }
3535
+ return this.createErrorResponse(`Failed to reorder elements: ${error.message}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3536
+ }
3537
+ }
3538
+ async copyElementSettings(args) {
3539
+ this.ensureAuthenticated();
3540
+ try {
3541
+ // Get current Elementor data using safe parsing utility
3542
+ const parsedResult = await this.safeGetElementorData(args.post_id);
3543
+ if (!parsedResult.success || !parsedResult.data) {
3544
+ return this.createErrorResponse(`Failed to get Elementor data for post/page ID ${args.post_id}: ${parsedResult.error || 'Unknown error'}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3545
+ }
3546
+ const elementorData = parsedResult.data;
3547
+ let sourceElement = null;
3548
+ let targetElement = null;
3549
+ // Function to find elements
3550
+ const findElement = (elements, elementId) => {
3551
+ for (let element of elements) {
3552
+ if (element.id === elementId) {
3553
+ return element;
3554
+ }
3555
+ if (element.elements && element.elements.length > 0) {
3556
+ const found = findElement(element.elements, elementId);
3557
+ if (found)
3558
+ return found;
3559
+ }
3560
+ }
3561
+ return null;
3562
+ };
3563
+ sourceElement = findElement(elementorData, args.source_element_id);
3564
+ targetElement = findElement(elementorData, args.target_element_id);
3565
+ if (!sourceElement) {
3566
+ return this.createErrorResponse(`Source element ID ${args.source_element_id} not found`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3567
+ }
3568
+ if (!targetElement) {
3569
+ return this.createErrorResponse("Target element ID ${args.target_element_id} not found", "OPERATION_ERROR", "API_ERROR", "Operation failed");
3570
+ }
3571
+ // Track what settings were copied and skipped
3572
+ let settingsCopied = [];
3573
+ let settingsSkipped = [];
3574
+ // Copy settings
3575
+ if (args.settings_to_copy && args.settings_to_copy.length > 0) {
3576
+ // Copy specific settings
3577
+ for (let setting of args.settings_to_copy) {
3578
+ if (sourceElement.settings && sourceElement.settings[setting] !== undefined) {
3579
+ if (!targetElement.settings)
3580
+ targetElement.settings = {};
3581
+ targetElement.settings[setting] = JSON.parse(JSON.stringify(sourceElement.settings[setting]));
3582
+ settingsCopied.push(setting);
3583
+ }
3584
+ else {
3585
+ settingsSkipped.push(setting);
3586
+ }
3587
+ }
3588
+ }
3589
+ else {
3590
+ // Copy all settings
3591
+ if (sourceElement.settings) {
3592
+ targetElement.settings = JSON.parse(JSON.stringify(sourceElement.settings));
3593
+ settingsCopied = Object.keys(sourceElement.settings);
3594
+ }
3595
+ }
3596
+ // Update the page
3597
+ await this.updateElementorData({
3598
+ post_id: args.post_id,
3599
+ elementor_data: JSON.stringify(elementorData)
3600
+ });
3601
+ return this.createSuccessResponse({
3602
+ post_id: args.post_id,
3603
+ source_element_id: args.source_element_id,
3604
+ source_element_type: sourceElement.elType,
3605
+ source_widget_type: sourceElement.widgetType || null,
3606
+ target_element_id: args.target_element_id,
3607
+ target_element_type: targetElement.elType,
3608
+ target_widget_type: targetElement.widgetType || null,
3609
+ operation: "settings_copy",
3610
+ settings_copied: settingsCopied,
3611
+ settings_skipped: settingsSkipped
3612
+ }, `Settings copied successfully from ${args.source_element_id} to ${args.target_element_id}`);
3613
+ }
3614
+ catch (error) {
3615
+ if (error instanceof McpError) {
3616
+ throw error;
3617
+ }
3618
+ return this.createErrorResponse(`Failed to copy element settings: ${error.message}`, "OPERATION_ERROR", "API_ERROR", "Operation failed");
3619
+ }
3620
+ }
3621
+ // Performance & Optimization
3622
+ async clearElementorCacheGeneral(args) {
3623
+ this.ensureAuthenticated();
3624
+ try {
3625
+ await this.clearElementorCache(args.post_id);
3626
+ return this.createSuccessResponse({
3627
+ operation: "cache_clear",
3628
+ scope: args.post_id ? "specific_post" : "general",
3629
+ post_id: args.post_id || null,
3630
+ cache_cleared: true
3631
+ }, args.post_id
3632
+ ? `Cache cleared successfully for post/page ID: ${args.post_id}`
3633
+ : "General Elementor cache cleared successfully");
3634
+ }
3635
+ catch (error) {
3636
+ return this.createErrorResponse(`Failed to clear cache: ${error.message}`, "CLEAR_CACHE_ERROR", "API_ERROR", `Cache clearing operation failed${args.post_id ? ` for post/page ${args.post_id}` : ''}`);
3637
+ }
3638
+ }
3639
+ // Advanced Element Operations
3640
+ async findElementsByType(args) {
3641
+ this.ensureAuthenticated();
3642
+ try {
3643
+ // Get current Elementor data using safe parsing utility
3644
+ const parsedResult = await this.safeGetElementorData(args.post_id);
3645
+ if (!parsedResult.success || !parsedResult.data) {
3646
+ return this.createErrorResponse(`No Elementor data found for post/page ID ${args.post_id}: ${parsedResult.error || 'Unknown error'}`, 'FIND_ELEMENTS_ERROR', 'API_ERROR', 'Failed to retrieve Elementor data');
3647
+ }
3648
+ const elementorData = parsedResult.data;
3649
+ const foundElements = [];
3650
+ // Function to find elements by type
3651
+ const findElementsByTypeRecursive = (elements) => {
3652
+ for (let element of elements) {
3653
+ if (element.widgetType === args.widget_type) {
3654
+ const result = {
3655
+ id: element.id,
3656
+ widgetType: element.widgetType
3657
+ };
3658
+ if (args.include_settings && element.settings) {
3659
+ result.settings = element.settings;
3660
+ }
3661
+ foundElements.push(result);
3662
+ }
3663
+ if (element.elements && element.elements.length > 0) {
3664
+ findElementsByTypeRecursive(element.elements);
3665
+ }
3666
+ }
3667
+ };
3668
+ findElementsByTypeRecursive(elementorData);
3669
+ return this.createSuccessResponse({
3670
+ post_id: args.post_id,
3671
+ widget_type: args.widget_type,
3672
+ found_elements: foundElements,
3673
+ total_found: foundElements.length,
3674
+ include_settings: args.include_settings || false
3675
+ }, `Found ${foundElements.length} elements of type "${args.widget_type}" in post/page ID ${args.post_id}`);
3676
+ }
3677
+ catch (error) {
3678
+ if (error instanceof McpError) {
3679
+ throw error;
3680
+ }
3681
+ return this.createErrorResponse(`Failed to find elements by type: ${error.message}`, "FIND_ELEMENTS_ERROR", "API_ERROR", "Operation failed");
3682
+ }
3683
+ }
3684
+ // Template Management (Requires Elementor Pro)
3685
+ async createElementorTemplate(args) {
3686
+ return this.createErrorResponse('Template management requires Elementor Pro API access. This feature is not available in the free version.', 'PRO_FEATURE_ERROR', 'FEATURE_UNAVAILABLE', 'Elementor Pro required');
3687
+ }
3688
+ async applyTemplateToPage(args) {
3689
+ return this.createErrorResponse('Template management requires Elementor Pro API access. This feature is not available in the free version.', 'PRO_FEATURE_ERROR', 'FEATURE_UNAVAILABLE', 'Elementor Pro required');
3690
+ }
3691
+ async exportElementorTemplate(args) {
3692
+ return this.createErrorResponse('Template management requires Elementor Pro API access. This feature is not available in the free version.', 'PRO_FEATURE_ERROR', 'FEATURE_UNAVAILABLE', 'Elementor Pro required');
3693
+ }
3694
+ async importElementorTemplate(args) {
3695
+ return this.createErrorResponse('Template management requires Elementor Pro API access. This feature is not available in the free version.', 'PRO_FEATURE_ERROR', 'FEATURE_UNAVAILABLE', 'Elementor Pro required');
3696
+ }
3697
+ // Global Settings (Requires Elementor Pro)
3698
+ async getElementorGlobalColors(args) {
3699
+ return this.createErrorResponse('Global settings require Elementor Pro API access. This feature is not available in the free version.', 'PRO_FEATURE_ERROR', 'FEATURE_UNAVAILABLE', 'Elementor Pro required');
3700
+ }
3701
+ async updateElementorGlobalColors(args) {
3702
+ return this.createErrorResponse('Global settings require Elementor Pro API access. This feature is not available in the free version.', 'PRO_FEATURE_ERROR', 'FEATURE_UNAVAILABLE', 'Elementor Pro required');
3703
+ }
3704
+ async getElementorGlobalFonts(args) {
3705
+ return this.createErrorResponse('Global settings require Elementor Pro API access. This feature is not available in the free version.', 'PRO_FEATURE_ERROR', 'FEATURE_UNAVAILABLE', 'Elementor Pro required');
3706
+ }
3707
+ async updateElementorGlobalFonts(args) {
3708
+ return this.createErrorResponse('Global settings require Elementor Pro API access. This feature is not available in the free version.', 'PRO_FEATURE_ERROR', 'FEATURE_UNAVAILABLE', 'Elementor Pro required');
3709
+ }
3710
+ // Advanced Operations (Not Yet Implemented)
3711
+ async rebuildPageStructure(args) {
3712
+ return this.createErrorResponse('Page structure rebuilding is a complex operation not yet implemented. Please use individual element manipulation tools instead.', 'NOT_IMPLEMENTED', 'FEATURE_UNAVAILABLE', 'Use individual element manipulation tools for page building');
3713
+ }
3714
+ async validateElementorData(args) {
3715
+ return this.createErrorResponse('Data validation not yet implemented. Please check data manually using get_elementor_data.', 'NOT_IMPLEMENTED', 'FEATURE_UNAVAILABLE', 'Use get_elementor_data to manually inspect page structure');
3716
+ }
3717
+ async regenerateCSS(args) {
3718
+ return this.createErrorResponse('CSS regeneration requires direct server access. Please use the Elementor admin interface: Elementor → Tools → Regenerate CSS & Data.', 'SERVER_ACCESS_REQUIRED', 'FEATURE_UNAVAILABLE', 'Use WordPress admin: Elementor → Tools → Regenerate CSS & Data');
3719
+ }
3720
+ async optimizeElementorAssets(args) {
3721
+ return this.createErrorResponse('Asset optimization requires direct server access. Please use WordPress optimization plugins or the Elementor admin interface.', 'SERVER_ACCESS_REQUIRED', 'FEATURE_UNAVAILABLE', 'Use WordPress optimization plugins or Elementor admin interface');
3722
+ }
3723
+ async bulkUpdateWidgetSettings(args) {
3724
+ return this.createErrorResponse('Bulk widget updates not yet implemented. Please use individual widget update tools or find_elements_by_type to identify widgets first.', 'NOT_IMPLEMENTED', 'FEATURE_UNAVAILABLE', 'Use individual widget update tools or find_elements_by_type');
3725
+ }
3726
+ async replaceWidgetContent(args) {
3727
+ return this.createErrorResponse('Widget content replacement not yet implemented. Please use individual widget update tools.', 'NOT_IMPLEMENTED', 'FEATURE_UNAVAILABLE', 'Use individual widget update tools');
3728
+ }
3729
+ async getElementorCustomFields(args) {
3730
+ return this.createErrorResponse('Custom fields integration not yet implemented. Please use WordPress REST API to access custom fields directly.', 'NOT_IMPLEMENTED', 'FEATURE_UNAVAILABLE', 'Use WordPress REST API for custom fields access');
3731
+ }
3732
+ async updateDynamicContentSources(args) {
3733
+ return this.createErrorResponse('Dynamic content management not yet implemented. Please update widget settings manually with dynamic field configurations.', 'NOT_IMPLEMENTED', 'FEATURE_UNAVAILABLE', 'Update widget settings manually with dynamic field configurations');
3734
+ }
3735
+ async getElementorRevisions(args) {
3736
+ return this.createErrorResponse('Revision management not yet implemented. Please use WordPress admin interface to access revisions.', 'NOT_IMPLEMENTED', 'FEATURE_UNAVAILABLE', 'Use WordPress admin interface for revision management');
3737
+ }
3738
+ async restoreElementorRevision(args) {
3739
+ return this.createErrorResponse('Revision management not yet implemented. Please use WordPress admin interface to restore revisions.', 'NOT_IMPLEMENTED', 'FEATURE_UNAVAILABLE', 'Use WordPress admin interface for revision management');
3740
+ }
3741
+ async compareElementorRevisions(args) {
3742
+ return this.createErrorResponse('Revision management not yet implemented. Please use WordPress admin interface to compare revisions.', 'NOT_IMPLEMENTED', 'FEATURE_UNAVAILABLE', 'Use WordPress admin interface for revision management');
3743
+ }
3744
+ async run() {
3745
+ const transport = new StdioServerTransport();
3746
+ await this.server.connect(transport);
3747
+ console.error('Elementor WordPress MCP server running on stdio');
3748
+ }
3749
+ }
3750
+ const server = new ElementorWordPressMCP();
3751
+ server.run().catch(console.error);
3752
+ //# sourceMappingURL=index-backup.js.map