myaidev-method 0.3.1 → 0.3.3

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 (56) hide show
  1. package/.claude-plugin/plugin.json +52 -48
  2. package/CHANGELOG.md +5 -0
  3. package/DEV_WORKFLOW_GUIDE.md +6 -6
  4. package/MCP_INTEGRATION.md +4 -4
  5. package/README.md +140 -66
  6. package/TECHNICAL_ARCHITECTURE.md +112 -18
  7. package/USER_GUIDE.md +270 -39
  8. package/bin/cli.js +47 -13
  9. package/dist/mcp/gutenberg-converter.js +667 -413
  10. package/dist/mcp/wordpress-admin-mcp.js +0 -1
  11. package/dist/mcp/wordpress-integration.js +0 -1
  12. package/dist/mcp/wordpress-server.js +1558 -1182
  13. package/dist/server/.tsbuildinfo +1 -1
  14. package/extension.json +3 -3
  15. package/package.json +9 -2
  16. package/skills/content-writer/SKILL.md +130 -178
  17. package/skills/infographic/SKILL.md +191 -0
  18. package/skills/myaidev-analyze/SKILL.md +242 -0
  19. package/skills/myaidev-architect/SKILL.md +389 -0
  20. package/skills/myaidev-coder/SKILL.md +291 -0
  21. package/skills/myaidev-debug/SKILL.md +308 -0
  22. package/skills/myaidev-documenter/SKILL.md +194 -0
  23. package/skills/myaidev-migrate/SKILL.md +300 -0
  24. package/skills/myaidev-performance/SKILL.md +270 -0
  25. package/skills/myaidev-refactor/SKILL.md +296 -0
  26. package/skills/myaidev-reviewer/SKILL.md +385 -0
  27. package/skills/myaidev-tester/SKILL.md +331 -0
  28. package/skills/myaidev-workflow/SKILL.md +567 -0
  29. package/skills/security-auditor/SKILL.md +1 -1
  30. package/src/cli/commands/addon.js +60 -12
  31. package/src/cli/commands/auth.js +10 -2
  32. package/src/config/workflows.js +11 -6
  33. package/src/lib/ascii-banner.js +3 -3
  34. package/src/lib/coolify-utils.js +0 -1
  35. package/src/lib/payloadcms-utils.js +0 -1
  36. package/src/lib/visual-generation-utils.js +0 -1
  37. package/src/lib/wordpress-admin-utils.js +0 -1
  38. package/src/mcp/gutenberg-converter.js +667 -413
  39. package/src/mcp/wordpress-admin-mcp.js +0 -1
  40. package/src/mcp/wordpress-integration.js +0 -1
  41. package/src/mcp/wordpress-server.js +1558 -1182
  42. package/src/scripts/test-coolify-deploy.js +0 -1
  43. package/src/statusline/statusline.sh +279 -0
  44. package/skills/content-writer/agents/editor-agent.md +0 -138
  45. package/skills/content-writer/agents/planner-agent.md +0 -121
  46. package/skills/content-writer/agents/research-agent.md +0 -83
  47. package/skills/content-writer/agents/seo-agent.md +0 -139
  48. package/skills/content-writer/agents/visual-planner-agent.md +0 -110
  49. package/skills/content-writer/agents/writer-agent.md +0 -85
  50. package/skills/sparc-architect/SKILL.md +0 -127
  51. package/skills/sparc-coder/SKILL.md +0 -90
  52. package/skills/sparc-documenter/SKILL.md +0 -155
  53. package/skills/sparc-reviewer/SKILL.md +0 -138
  54. package/skills/sparc-tester/SKILL.md +0 -100
  55. package/skills/sparc-workflow/SKILL.md +0 -130
  56. /package/{marketplace.json → .claude-plugin/marketplace.json} +0 -0
@@ -3,8 +3,9 @@
3
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
5
  import { z } from "zod";
6
- import fetch from "node-fetch";
7
6
  import dotenv from "dotenv";
7
+ import fs from "node:fs";
8
+ import path from "node:path";
8
9
  import { GutenbergConverter } from "./gutenberg-converter.js";
9
10
 
10
11
  // Load environment variables
@@ -14,1260 +15,1635 @@ dotenv.config();
14
15
  const WORDPRESS_URL = process.env.WORDPRESS_URL;
15
16
  const WORDPRESS_USERNAME = process.env.WORDPRESS_USERNAME;
16
17
  const WORDPRESS_APP_PASSWORD = process.env.WORDPRESS_APP_PASSWORD;
17
- const WORDPRESS_USE_GUTENBERG = process.env.WORDPRESS_USE_GUTENBERG === 'true';
18
+ const WORDPRESS_USE_GUTENBERG = process.env.WORDPRESS_USE_GUTENBERG === "true";
18
19
 
19
20
  // Validate environment variables
20
21
  if (!WORDPRESS_URL || !WORDPRESS_USERNAME || !WORDPRESS_APP_PASSWORD) {
21
- console.error("Missing required environment variables: WORDPRESS_URL, WORDPRESS_USERNAME, WORDPRESS_APP_PASSWORD");
22
- process.exit(1);
22
+ console.error(
23
+ "Missing required environment variables: WORDPRESS_URL, WORDPRESS_USERNAME, WORDPRESS_APP_PASSWORD",
24
+ );
25
+ process.exit(1);
23
26
  }
24
27
 
25
28
  // Session management
26
29
  class SessionManager {
27
- constructor() {
28
- this.sessions = new Map();
29
- this.activeSession = null;
30
- this.sessionTimeout = 30 * 60 * 1000; // 30 minutes
31
- }
32
-
33
- createSession(id = null) {
34
- const sessionId = id || `wp_session_${Date.now()}`;
35
- const session = {
36
- id: sessionId,
37
- created: Date.now(),
38
- lastActivity: Date.now(),
39
- operations: [],
40
- context: {}
41
- };
42
-
43
- this.sessions.set(sessionId, session);
44
- this.activeSession = sessionId;
45
- return sessionId;
46
- }
47
-
48
- getSession(id) {
49
- return this.sessions.get(id);
50
- }
51
-
52
- updateActivity(id) {
53
- const session = this.sessions.get(id);
54
- if (session) {
55
- session.lastActivity = Date.now();
56
- }
57
- }
58
-
59
- logOperation(sessionId, operation) {
60
- const session = this.sessions.get(sessionId);
61
- if (session) {
62
- session.operations.push({
63
- ...operation,
64
- timestamp: Date.now()
65
- });
66
- }
67
- }
68
-
69
- cleanup() {
70
- const now = Date.now();
71
- for (const [id, session] of this.sessions) {
72
- if (now - session.lastActivity > this.sessionTimeout) {
73
- this.sessions.delete(id);
74
- }
75
- }
76
- }
30
+ constructor() {
31
+ this.sessions = new Map();
32
+ this.activeSession = null;
33
+ this.sessionTimeout = 30 * 60 * 1000; // 30 minutes
34
+ }
35
+
36
+ createSession(id = null) {
37
+ const sessionId = id || `wp_session_${Date.now()}`;
38
+ const session = {
39
+ id: sessionId,
40
+ created: Date.now(),
41
+ lastActivity: Date.now(),
42
+ operations: [],
43
+ context: {},
44
+ };
45
+
46
+ this.sessions.set(sessionId, session);
47
+ this.activeSession = sessionId;
48
+ return sessionId;
49
+ }
50
+
51
+ getSession(id) {
52
+ return this.sessions.get(id);
53
+ }
54
+
55
+ updateActivity(id) {
56
+ const session = this.sessions.get(id);
57
+ if (session) {
58
+ session.lastActivity = Date.now();
59
+ }
60
+ }
61
+
62
+ logOperation(sessionId, operation) {
63
+ const session = this.sessions.get(sessionId);
64
+ if (session) {
65
+ session.operations.push({
66
+ ...operation,
67
+ timestamp: Date.now(),
68
+ });
69
+ }
70
+ }
71
+
72
+ cleanup() {
73
+ const now = Date.now();
74
+ for (const [id, session] of this.sessions) {
75
+ if (now - session.lastActivity > this.sessionTimeout) {
76
+ this.sessions.delete(id);
77
+ }
78
+ }
79
+ }
77
80
  }
78
81
 
79
82
  const sessionManager = new SessionManager();
80
83
 
81
84
  // Memory manager for persistence (simple implementation)
82
85
  class MemoryManager {
83
- constructor() {
84
- this.memory = new Map();
85
- this.namespaces = new Map();
86
- }
87
-
88
- store(key, value, namespace = 'default', ttl = null) {
89
- const nsMap = this.namespaces.get(namespace) || new Map();
90
- const entry = {
91
- value,
92
- timestamp: Date.now(),
93
- ttl: ttl ? Date.now() + ttl : null
94
- };
95
- nsMap.set(key, entry);
96
- this.namespaces.set(namespace, nsMap);
97
- return true;
98
- }
99
-
100
- retrieve(key, namespace = 'default') {
101
- const nsMap = this.namespaces.get(namespace);
102
- if (!nsMap) return null;
103
-
104
- const entry = nsMap.get(key);
105
- if (!entry) return null;
106
-
107
- if (entry.ttl && Date.now() > entry.ttl) {
108
- nsMap.delete(key);
109
- return null;
110
- }
111
-
112
- return entry.value;
113
- }
114
-
115
- delete(key, namespace = 'default') {
116
- const nsMap = this.namespaces.get(namespace);
117
- if (nsMap) {
118
- return nsMap.delete(key);
119
- }
120
- return false;
121
- }
122
-
123
- list(namespace = 'default') {
124
- const nsMap = this.namespaces.get(namespace);
125
- if (!nsMap) return [];
126
-
127
- const now = Date.now();
128
- const results = [];
129
-
130
- for (const [key, entry] of nsMap) {
131
- if (entry.ttl && now > entry.ttl) {
132
- nsMap.delete(key);
133
- continue;
134
- }
135
- results.push({ key, value: entry.value, timestamp: entry.timestamp });
136
- }
137
-
138
- return results;
139
- }
86
+ constructor() {
87
+ this.memory = new Map();
88
+ this.namespaces = new Map();
89
+ }
90
+
91
+ store(key, value, namespace = "default", ttl = null) {
92
+ const nsMap = this.namespaces.get(namespace) || new Map();
93
+ const entry = {
94
+ value,
95
+ timestamp: Date.now(),
96
+ ttl: ttl ? Date.now() + ttl : null,
97
+ };
98
+ nsMap.set(key, entry);
99
+ this.namespaces.set(namespace, nsMap);
100
+ return true;
101
+ }
102
+
103
+ retrieve(key, namespace = "default") {
104
+ const nsMap = this.namespaces.get(namespace);
105
+ if (!nsMap) return null;
106
+
107
+ const entry = nsMap.get(key);
108
+ if (!entry) return null;
109
+
110
+ if (entry.ttl && Date.now() > entry.ttl) {
111
+ nsMap.delete(key);
112
+ return null;
113
+ }
114
+
115
+ return entry.value;
116
+ }
117
+
118
+ delete(key, namespace = "default") {
119
+ const nsMap = this.namespaces.get(namespace);
120
+ if (nsMap) {
121
+ return nsMap.delete(key);
122
+ }
123
+ return false;
124
+ }
125
+
126
+ list(namespace = "default") {
127
+ const nsMap = this.namespaces.get(namespace);
128
+ if (!nsMap) return [];
129
+
130
+ const now = Date.now();
131
+ const results = [];
132
+
133
+ for (const [key, entry] of nsMap) {
134
+ if (entry.ttl && now > entry.ttl) {
135
+ nsMap.delete(key);
136
+ continue;
137
+ }
138
+ results.push({ key, value: entry.value, timestamp: entry.timestamp });
139
+ }
140
+
141
+ return results;
142
+ }
140
143
  }
141
144
 
142
145
  const memoryManager = new MemoryManager();
143
146
 
144
147
  // Create MCP server with enhanced configuration
145
148
  const server = new McpServer({
146
- name: "wordpress-mcp-server",
147
- version: "2.0.0",
148
- description: "Enhanced WordPress MCP Server with session management and memory persistence"
149
+ name: "wordpress-mcp-server",
150
+ version: "2.0.0",
151
+ description:
152
+ "Enhanced WordPress MCP Server with session management and memory persistence",
149
153
  });
150
154
 
151
155
  // Enhanced helper function for WordPress API requests with better error handling
152
- async function wordpressRequest(endpoint, method = 'GET', data = null, sessionId = null) {
153
- const baseUrl = WORDPRESS_URL.replace(/\/$/, '');
154
- const apiPath = '/wp-json/wp/v2';
155
- const auth = Buffer.from(`${WORDPRESS_USERNAME}:${WORDPRESS_APP_PASSWORD}`).toString('base64');
156
-
157
- const options = {
158
- method,
159
- headers: {
160
- 'Authorization': `Basic ${auth}`,
161
- 'Content-Type': 'application/json',
162
- 'User-Agent': 'WordPress-MCP-Server/2.0.0'
163
- },
164
- timeout: 30000 // 30 second timeout
165
- };
166
-
167
- if (data && method !== 'GET') {
168
- options.body = JSON.stringify(data);
169
- }
170
-
171
- const startTime = Date.now();
172
- let response;
173
-
174
- try {
175
- response = await fetch(`${baseUrl}${apiPath}${endpoint}`, options);
176
-
177
- // Log operation if session provided
178
- if (sessionId) {
179
- sessionManager.logOperation(sessionId, {
180
- type: 'api_request',
181
- method,
182
- endpoint,
183
- status: response.status,
184
- duration: Date.now() - startTime
185
- });
186
- sessionManager.updateActivity(sessionId);
187
- }
188
-
189
- if (!response.ok) {
190
- let errorBody;
191
- try {
192
- errorBody = await response.json();
193
- } catch {
194
- errorBody = await response.text();
195
- }
196
-
197
- throw new MCPError({
198
- code: response.status,
199
- message: `WordPress API Error: ${response.status} - ${response.statusText}`,
200
- details: errorBody,
201
- endpoint,
202
- method
203
- });
204
- }
205
-
206
- return await response.json();
207
-
208
- } catch (error) {
209
- // Log error operation
210
- if (sessionId) {
211
- sessionManager.logOperation(sessionId, {
212
- type: 'api_error',
213
- method,
214
- endpoint,
215
- error: error.message,
216
- duration: Date.now() - startTime
217
- });
218
- }
219
-
220
- if (error instanceof MCPError) {
221
- throw error;
222
- }
223
-
224
- throw new MCPError({
225
- code: 'NETWORK_ERROR',
226
- message: `Network error: ${error.message}`,
227
- details: error,
228
- endpoint,
229
- method
230
- });
231
- }
156
+ async function wordpressRequest(
157
+ endpoint,
158
+ method = "GET",
159
+ data = null,
160
+ sessionId = null,
161
+ ) {
162
+ const baseUrl = WORDPRESS_URL.replace(/\/$/, "");
163
+ const apiPath = "/wp-json/wp/v2";
164
+ const auth = Buffer.from(
165
+ `${WORDPRESS_USERNAME}:${WORDPRESS_APP_PASSWORD}`,
166
+ ).toString("base64");
167
+
168
+ const options = {
169
+ method,
170
+ headers: {
171
+ Authorization: `Basic ${auth}`,
172
+ "Content-Type": "application/json",
173
+ "User-Agent": "WordPress-MCP-Server/2.0.0",
174
+ },
175
+ timeout: 30000, // 30 second timeout
176
+ };
177
+
178
+ if (data && method !== "GET") {
179
+ options.body = JSON.stringify(data);
180
+ }
181
+
182
+ const startTime = Date.now();
183
+ let response;
184
+
185
+ try {
186
+ response = await fetch(`${baseUrl}${apiPath}${endpoint}`, options);
187
+
188
+ // Log operation if session provided
189
+ if (sessionId) {
190
+ sessionManager.logOperation(sessionId, {
191
+ type: "api_request",
192
+ method,
193
+ endpoint,
194
+ status: response.status,
195
+ duration: Date.now() - startTime,
196
+ });
197
+ sessionManager.updateActivity(sessionId);
198
+ }
199
+
200
+ if (!response.ok) {
201
+ let errorBody;
202
+ try {
203
+ errorBody = await response.json();
204
+ } catch {
205
+ errorBody = await response.text();
206
+ }
207
+
208
+ throw new MCPError({
209
+ code: response.status,
210
+ message: `WordPress API Error: ${response.status} - ${response.statusText}`,
211
+ details: errorBody,
212
+ endpoint,
213
+ method,
214
+ });
215
+ }
216
+
217
+ return await response.json();
218
+ } catch (error) {
219
+ // Log error operation
220
+ if (sessionId) {
221
+ sessionManager.logOperation(sessionId, {
222
+ type: "api_error",
223
+ method,
224
+ endpoint,
225
+ error: error.message,
226
+ duration: Date.now() - startTime,
227
+ });
228
+ }
229
+
230
+ if (error instanceof MCPError) {
231
+ throw error;
232
+ }
233
+
234
+ throw new MCPError({
235
+ code: "NETWORK_ERROR",
236
+ message: `Network error: ${error.message}`,
237
+ details: error,
238
+ endpoint,
239
+ method,
240
+ });
241
+ }
242
+ }
243
+
244
+ // Helper for uploading files to WordPress media library via multipart/form-data
245
+ async function wordpressUploadRequest(
246
+ filePath,
247
+ metadata = {},
248
+ sessionId = null,
249
+ ) {
250
+ const baseUrl = WORDPRESS_URL.replace(/\/$/, "");
251
+ const apiPath = "/wp-json/wp/v2";
252
+ const auth = Buffer.from(
253
+ `${WORDPRESS_USERNAME}:${WORDPRESS_APP_PASSWORD}`,
254
+ ).toString("base64");
255
+
256
+ const fileBuffer = fs.readFileSync(filePath);
257
+ const fileName = path.basename(filePath);
258
+
259
+ const formData = new FormData();
260
+ formData.append("file", new Blob([fileBuffer]), fileName);
261
+ if (metadata.title) formData.append("title", metadata.title);
262
+ if (metadata.alt_text) formData.append("alt_text", metadata.alt_text);
263
+ if (metadata.caption) formData.append("caption", metadata.caption);
264
+ if (metadata.description)
265
+ formData.append("description", metadata.description);
266
+
267
+ const startTime = Date.now();
268
+
269
+ try {
270
+ const response = await fetch(`${baseUrl}${apiPath}/media`, {
271
+ method: "POST",
272
+ headers: {
273
+ Authorization: `Basic ${auth}`,
274
+ "User-Agent": "WordPress-MCP-Server/2.0.0",
275
+ },
276
+ body: formData,
277
+ timeout: 60000,
278
+ });
279
+
280
+ if (sessionId) {
281
+ sessionManager.logOperation(sessionId, {
282
+ type: "media_upload",
283
+ file: fileName,
284
+ status: response.status,
285
+ duration: Date.now() - startTime,
286
+ });
287
+ sessionManager.updateActivity(sessionId);
288
+ }
289
+
290
+ if (!response.ok) {
291
+ let errorBody;
292
+ try {
293
+ errorBody = await response.json();
294
+ } catch {
295
+ errorBody = await response.text();
296
+ }
297
+ throw new MCPError({
298
+ code: response.status,
299
+ message: `Media upload failed: ${response.status} - ${response.statusText}`,
300
+ details: errorBody,
301
+ endpoint: "/media",
302
+ method: "POST",
303
+ });
304
+ }
305
+
306
+ return await response.json();
307
+ } catch (error) {
308
+ if (sessionId) {
309
+ sessionManager.logOperation(sessionId, {
310
+ type: "media_upload_error",
311
+ file: fileName,
312
+ error: error.message,
313
+ duration: Date.now() - startTime,
314
+ });
315
+ }
316
+ if (error instanceof MCPError) throw error;
317
+ throw new MCPError({
318
+ code: "UPLOAD_ERROR",
319
+ message: `Media upload error: ${error.message}`,
320
+ details: error,
321
+ endpoint: "/media",
322
+ method: "POST",
323
+ });
324
+ }
325
+ }
326
+
327
+ // Upload local assets referenced in content and replace paths with WordPress URLs
328
+ async function uploadAndReplaceAssets(content, basePath, sessionId = null) {
329
+ // Match both HTML <img> tags and markdown ![alt](path) image syntax
330
+ const patterns = [
331
+ /<img\s+[^>]*src=["'](assets\/[^"']+)["'][^>]*>/gi,
332
+ /!\[[^\]]*\]\((assets\/[^)]+)\)/gi,
333
+ ];
334
+ const uploadedAssets = [];
335
+ let match;
336
+ const replacements = new Map();
337
+
338
+ for (const imgRegex of patterns) {
339
+ while ((match = imgRegex.exec(content)) !== null) {
340
+ const relativePath = match[1];
341
+ if (replacements.has(relativePath)) continue;
342
+
343
+ const absolutePath = path.resolve(basePath, relativePath);
344
+ if (!fs.existsSync(absolutePath)) {
345
+ uploadedAssets.push({
346
+ file: relativePath,
347
+ status: "skipped",
348
+ reason: "file not found",
349
+ });
350
+ continue;
351
+ }
352
+
353
+ try {
354
+ const fileName = path.basename(
355
+ relativePath,
356
+ path.extname(relativePath),
357
+ );
358
+ const title = fileName.replace(/[-_]/g, " ");
359
+ const media = await wordpressUploadRequest(
360
+ absolutePath,
361
+ { title },
362
+ sessionId,
363
+ );
364
+ replacements.set(relativePath, media.source_url);
365
+ uploadedAssets.push({
366
+ file: relativePath,
367
+ status: "uploaded",
368
+ media_id: media.id,
369
+ source_url: media.source_url,
370
+ });
371
+ } catch (error) {
372
+ uploadedAssets.push({
373
+ file: relativePath,
374
+ status: "error",
375
+ error: error.message,
376
+ });
377
+ }
378
+ }
379
+ }
380
+
381
+ let updatedContent = content;
382
+ for (const [localPath, wpUrl] of replacements) {
383
+ updatedContent = updatedContent.replaceAll(localPath, wpUrl);
384
+ }
385
+
386
+ return { content: updatedContent, uploadedAssets };
232
387
  }
233
388
 
234
389
  // Custom error class for better error handling
235
390
  class MCPError extends Error {
236
- constructor({ code, message, details, endpoint, method }) {
237
- super(message);
238
- this.name = 'MCPError';
239
- this.code = code;
240
- this.details = details;
241
- this.endpoint = endpoint;
242
- this.method = method;
243
- }
244
-
245
- toJSON() {
246
- return {
247
- success: false,
248
- error: {
249
- code: this.code,
250
- message: this.message,
251
- details: this.details,
252
- endpoint: this.endpoint,
253
- method: this.method,
254
- timestamp: Date.now()
255
- }
256
- };
257
- }
391
+ constructor({ code, message, details, endpoint, method }) {
392
+ super(message);
393
+ this.name = "MCPError";
394
+ this.code = code;
395
+ this.details = details;
396
+ this.endpoint = endpoint;
397
+ this.method = method;
398
+ }
399
+
400
+ toJSON() {
401
+ return {
402
+ success: false,
403
+ error: {
404
+ code: this.code,
405
+ message: this.message,
406
+ details: this.details,
407
+ endpoint: this.endpoint,
408
+ method: this.method,
409
+ timestamp: Date.now(),
410
+ },
411
+ };
412
+ }
258
413
  }
259
414
 
260
415
  // Enhanced response formatter
261
416
  function formatMCPResponse(data, success = true) {
262
- const response = {
263
- success,
264
- timestamp: Date.now(),
265
- server_version: "2.0.0"
266
- };
267
-
268
- if (success) {
269
- response.data = data;
270
- } else {
271
- response.error = data;
272
- }
273
-
274
- return {
275
- content: [{
276
- type: "text",
277
- text: JSON.stringify(response, null, 2)
278
- }]
279
- };
417
+ const response = {
418
+ success,
419
+ timestamp: Date.now(),
420
+ server_version: "2.0.0",
421
+ };
422
+
423
+ if (success) {
424
+ response.data = data;
425
+ } else {
426
+ response.error = data;
427
+ }
428
+
429
+ return {
430
+ content: [
431
+ {
432
+ type: "text",
433
+ text: JSON.stringify(response, null, 2),
434
+ },
435
+ ],
436
+ };
280
437
  }
281
438
 
282
439
  // Tool execution wrapper with enhanced error handling
283
440
  async function executeTool(toolName, params, handler) {
284
- const sessionId = sessionManager.activeSession || sessionManager.createSession();
285
-
286
- try {
287
- sessionManager.updateActivity(sessionId);
288
-
289
- const startTime = Date.now();
290
- const result = await handler(params, sessionId);
291
- const duration = Date.now() - startTime;
292
-
293
- // Log successful operation
294
- sessionManager.logOperation(sessionId, {
295
- type: 'tool_execution',
296
- tool: toolName,
297
- status: 'success',
298
- duration,
299
- params: Object.keys(params || {})
300
- });
301
-
302
- return result;
303
-
304
- } catch (error) {
305
- // Log failed operation
306
- sessionManager.logOperation(sessionId, {
307
- type: 'tool_execution',
308
- tool: toolName,
309
- status: 'error',
310
- error: error.message,
311
- params: Object.keys(params || {})
312
- });
313
-
314
- if (error instanceof MCPError) {
315
- return formatMCPResponse(error.toJSON().error, false);
316
- }
317
-
318
- return formatMCPResponse({
319
- code: 'TOOL_ERROR',
320
- message: error.message,
321
- tool: toolName,
322
- timestamp: Date.now()
323
- }, false);
324
- }
441
+ const sessionId =
442
+ sessionManager.activeSession || sessionManager.createSession();
443
+
444
+ try {
445
+ sessionManager.updateActivity(sessionId);
446
+
447
+ const startTime = Date.now();
448
+ const result = await handler(params, sessionId);
449
+ const duration = Date.now() - startTime;
450
+
451
+ // Log successful operation
452
+ sessionManager.logOperation(sessionId, {
453
+ type: "tool_execution",
454
+ tool: toolName,
455
+ status: "success",
456
+ duration,
457
+ params: Object.keys(params || {}),
458
+ });
459
+
460
+ return result;
461
+ } catch (error) {
462
+ // Log failed operation
463
+ sessionManager.logOperation(sessionId, {
464
+ type: "tool_execution",
465
+ tool: toolName,
466
+ status: "error",
467
+ error: error.message,
468
+ params: Object.keys(params || {}),
469
+ });
470
+
471
+ if (error instanceof MCPError) {
472
+ return formatMCPResponse(error.toJSON().error, false);
473
+ }
474
+
475
+ return formatMCPResponse(
476
+ {
477
+ code: "TOOL_ERROR",
478
+ message: error.message,
479
+ tool: toolName,
480
+ timestamp: Date.now(),
481
+ },
482
+ false,
483
+ );
484
+ }
325
485
  }
326
486
 
327
487
  // Register enhanced WordPress MCP Tools
328
488
 
329
489
  // Session Management Tools
330
- server.registerTool("wp_session_create", {
331
- title: "Create WordPress Session",
332
- description: "Create a new session for tracking operations and maintaining context",
333
- inputSchema: {
334
- type: "object",
335
- properties: {
336
- session_id: {
337
- type: "string",
338
- description: "Optional custom session ID"
339
- },
340
- context: {
341
- type: "object",
342
- description: "Optional context data for the session"
343
- }
344
- },
345
- additionalProperties: false
346
- }
347
- }, async (params) => {
348
- return executeTool('wp_session_create', params, async (params) => {
349
- const sessionId = sessionManager.createSession(params.session_id);
350
- if (params.context) {
351
- const session = sessionManager.getSession(sessionId);
352
- session.context = { ...session.context, ...params.context };
353
- }
354
-
355
- return formatMCPResponse({
356
- session_id: sessionId,
357
- created: true,
358
- message: "Session created successfully"
359
- });
360
- });
361
- });
362
-
363
- server.registerTool("wp_session_status", {
364
- title: "Get WordPress Session Status",
365
- description: "Get current session information and operation history",
366
- inputSchema: {
367
- type: "object",
368
- properties: {
369
- session_id: {
370
- type: "string",
371
- description: "Session ID to check status for"
372
- }
373
- },
374
- additionalProperties: false
375
- }
376
- }, async (params) => {
377
- return executeTool('wp_session_status', params, async (params) => {
378
- const sessionId = params.session_id || sessionManager.activeSession;
379
- if (!sessionId) {
380
- throw new MCPError({
381
- code: 'NO_SESSION',
382
- message: 'No active session found'
383
- });
384
- }
385
-
386
- const session = sessionManager.getSession(sessionId);
387
- if (!session) {
388
- throw new MCPError({
389
- code: 'SESSION_NOT_FOUND',
390
- message: `Session ${sessionId} not found`
391
- });
392
- }
393
-
394
- return formatMCPResponse({
395
- session: {
396
- id: session.id,
397
- created: new Date(session.created).toISOString(),
398
- last_activity: new Date(session.lastActivity).toISOString(),
399
- operations_count: session.operations.length,
400
- recent_operations: session.operations.slice(-5),
401
- context: session.context
402
- }
403
- });
404
- });
405
- });
490
+ server.registerTool(
491
+ "wp_session_create",
492
+ {
493
+ title: "Create WordPress Session",
494
+ description:
495
+ "Create a new session for tracking operations and maintaining context",
496
+ inputSchema: z.object({
497
+ session_id: z.string().optional().describe("Optional custom session ID"),
498
+ context: z
499
+ .object({})
500
+ .passthrough()
501
+ .optional()
502
+ .describe("Optional context data for the session"),
503
+ }),
504
+ },
505
+ async (params) => {
506
+ return executeTool("wp_session_create", params, async (params) => {
507
+ const sessionId = sessionManager.createSession(params.session_id);
508
+ if (params.context) {
509
+ const session = sessionManager.getSession(sessionId);
510
+ session.context = { ...session.context, ...params.context };
511
+ }
512
+
513
+ return formatMCPResponse({
514
+ session_id: sessionId,
515
+ created: true,
516
+ message: "Session created successfully",
517
+ });
518
+ });
519
+ },
520
+ );
521
+
522
+ server.registerTool(
523
+ "wp_session_status",
524
+ {
525
+ title: "Get WordPress Session Status",
526
+ description: "Get current session information and operation history",
527
+ inputSchema: z.object({
528
+ session_id: z
529
+ .string()
530
+ .optional()
531
+ .describe("Session ID to check status for"),
532
+ }),
533
+ },
534
+ async (params) => {
535
+ return executeTool("wp_session_status", params, async (params) => {
536
+ const sessionId = params.session_id || sessionManager.activeSession;
537
+ if (!sessionId) {
538
+ throw new MCPError({
539
+ code: "NO_SESSION",
540
+ message: "No active session found",
541
+ });
542
+ }
543
+
544
+ const session = sessionManager.getSession(sessionId);
545
+ if (!session) {
546
+ throw new MCPError({
547
+ code: "SESSION_NOT_FOUND",
548
+ message: `Session ${sessionId} not found`,
549
+ });
550
+ }
551
+
552
+ return formatMCPResponse({
553
+ session: {
554
+ id: session.id,
555
+ created: new Date(session.created).toISOString(),
556
+ last_activity: new Date(session.lastActivity).toISOString(),
557
+ operations_count: session.operations.length,
558
+ recent_operations: session.operations.slice(-5),
559
+ context: session.context,
560
+ },
561
+ });
562
+ });
563
+ },
564
+ );
406
565
 
407
566
  // Memory Management Tools
408
- server.registerTool("wp_memory_store", {
409
- title: "Store WordPress Memory",
410
- description: "Store data in memory for persistence across operations",
411
- inputSchema: {
412
- type: "object",
413
- properties: {
414
- key: {
415
- type: "string",
416
- description: "Memory key to store data under"
417
- },
418
- value: {
419
- description: "Value to store (any type)"
420
- },
421
- namespace: {
422
- type: "string",
423
- description: "Namespace to organize data",
424
- default: "wordpress"
425
- },
426
- ttl: {
427
- type: "number",
428
- description: "Time-to-live in milliseconds"
429
- }
430
- },
431
- required: ["key", "value"],
432
- additionalProperties: false
433
- }
434
- }, async (params) => {
435
- return executeTool('wp_memory_store', params, async (params) => {
436
- const stored = memoryManager.store(
437
- params.key,
438
- params.value,
439
- params.namespace,
440
- params.ttl
441
- );
442
-
443
- return formatMCPResponse({
444
- stored: stored,
445
- key: params.key,
446
- namespace: params.namespace,
447
- ttl: params.ttl,
448
- message: "Data stored successfully"
449
- });
450
- });
451
- });
452
-
453
- server.registerTool("wp_memory_retrieve", {
454
- title: "Retrieve WordPress Memory",
455
- description: "Retrieve stored data from memory",
456
- inputSchema: {
457
- type: "object",
458
- properties: {
459
- key: {
460
- type: "string",
461
- description: "Memory key to retrieve"
462
- },
463
- namespace: {
464
- type: "string",
465
- description: "Namespace to search in",
466
- default: "wordpress"
467
- }
468
- },
469
- required: ["key"],
470
- additionalProperties: false
471
- }
472
- }, async (params) => {
473
- return executeTool('wp_memory_retrieve', params, async (params) => {
474
- const value = memoryManager.retrieve(params.key, params.namespace);
475
-
476
- return formatMCPResponse({
477
- key: params.key,
478
- namespace: params.namespace,
479
- value: value,
480
- found: value !== null
481
- });
482
- });
483
- });
484
-
485
- server.registerTool("wp_memory_list", {
486
- title: "List WordPress Memory",
487
- description: "List all stored data in a namespace",
488
- inputSchema: {
489
- type: "object",
490
- properties: {
491
- namespace: {
492
- type: "string",
493
- description: "Namespace to list data from",
494
- default: "wordpress"
495
- }
496
- },
497
- additionalProperties: false
498
- }
499
- }, async (params) => {
500
- return executeTool('wp_memory_list', params, async (params) => {
501
- const entries = memoryManager.list(params.namespace);
502
-
503
- return formatMCPResponse({
504
- namespace: params.namespace,
505
- entries: entries,
506
- count: entries.length
507
- });
508
- });
509
- });
567
+ server.registerTool(
568
+ "wp_memory_store",
569
+ {
570
+ title: "Store WordPress Memory",
571
+ description: "Store data in memory for persistence across operations",
572
+ inputSchema: z.object({
573
+ key: z.string().describe("Memory key to store data under"),
574
+ value: z.any().describe("Value to store (any type)"),
575
+ namespace: z
576
+ .string()
577
+ .optional()
578
+ .default("wordpress")
579
+ .describe("Namespace to organize data"),
580
+ ttl: z.number().optional().describe("Time-to-live in milliseconds"),
581
+ }),
582
+ },
583
+ async (params) => {
584
+ return executeTool("wp_memory_store", params, async (params) => {
585
+ const stored = memoryManager.store(
586
+ params.key,
587
+ params.value,
588
+ params.namespace,
589
+ params.ttl,
590
+ );
591
+
592
+ return formatMCPResponse({
593
+ stored: stored,
594
+ key: params.key,
595
+ namespace: params.namespace,
596
+ ttl: params.ttl,
597
+ message: "Data stored successfully",
598
+ });
599
+ });
600
+ },
601
+ );
602
+
603
+ server.registerTool(
604
+ "wp_memory_retrieve",
605
+ {
606
+ title: "Retrieve WordPress Memory",
607
+ description: "Retrieve stored data from memory",
608
+ inputSchema: z.object({
609
+ key: z.string().describe("Memory key to retrieve"),
610
+ namespace: z
611
+ .string()
612
+ .optional()
613
+ .default("wordpress")
614
+ .describe("Namespace to search in"),
615
+ }),
616
+ },
617
+ async (params) => {
618
+ return executeTool("wp_memory_retrieve", params, async (params) => {
619
+ const value = memoryManager.retrieve(params.key, params.namespace);
620
+
621
+ return formatMCPResponse({
622
+ key: params.key,
623
+ namespace: params.namespace,
624
+ value: value,
625
+ found: value !== null,
626
+ });
627
+ });
628
+ },
629
+ );
630
+
631
+ server.registerTool(
632
+ "wp_memory_list",
633
+ {
634
+ title: "List WordPress Memory",
635
+ description: "List all stored data in a namespace",
636
+ inputSchema: z.object({
637
+ namespace: z
638
+ .string()
639
+ .optional()
640
+ .default("wordpress")
641
+ .describe("Namespace to list data from"),
642
+ }),
643
+ },
644
+ async (params) => {
645
+ return executeTool("wp_memory_list", params, async (params) => {
646
+ const entries = memoryManager.list(params.namespace);
647
+
648
+ return formatMCPResponse({
649
+ namespace: params.namespace,
650
+ entries: entries,
651
+ count: entries.length,
652
+ });
653
+ });
654
+ },
655
+ );
510
656
 
511
657
  // Health and System Tools
512
- server.registerTool("wp_health_check", {
513
- title: "WordPress Health Check",
514
- description: "Comprehensive health check of WordPress site and MCP server",
515
- inputSchema: {
516
- type: "object",
517
- properties: {
518
- detailed: {
519
- type: "boolean",
520
- description: "Include detailed health metrics",
521
- default: false
522
- }
523
- },
524
- additionalProperties: false
525
- }
526
- }, async (params) => {
527
- return executeTool('wp_health_check', params, async (params, sessionId) => {
528
- const checks = [];
529
-
530
- try {
531
- // Test WordPress API connectivity
532
- const startTime = Date.now();
533
- const response = await wordpressRequest('/', 'GET', null, sessionId);
534
- checks.push({
535
- name: 'WordPress API Connectivity',
536
- status: 'passed',
537
- response_time: Date.now() - startTime,
538
- message: 'API is responding'
539
- });
540
- } catch (error) {
541
- checks.push({
542
- name: 'WordPress API Connectivity',
543
- status: 'failed',
544
- error: error.message
545
- });
546
- }
547
-
548
- // Check authentication
549
- try {
550
- await wordpressRequest('/users/me', 'GET', null, sessionId);
551
- checks.push({
552
- name: 'Authentication',
553
- status: 'passed',
554
- message: 'Authentication working'
555
- });
556
- } catch (error) {
557
- checks.push({
558
- name: 'Authentication',
559
- status: 'failed',
560
- error: error.message
561
- });
562
- }
563
-
564
- // Session status
565
- const activeSessionCount = sessionManager.sessions.size;
566
- checks.push({
567
- name: 'Session Management',
568
- status: 'passed',
569
- active_sessions: activeSessionCount,
570
- current_session: sessionId
571
- });
572
-
573
- // Memory status
574
- const memoryEntries = memoryManager.list('wordpress');
575
- checks.push({
576
- name: 'Memory Management',
577
- status: 'passed',
578
- stored_entries: memoryEntries.length
579
- });
580
-
581
- const overall = checks.every(check => check.status === 'passed');
582
-
583
- return formatMCPResponse({
584
- overall_status: overall ? 'healthy' : 'unhealthy',
585
- checks: checks,
586
- timestamp: Date.now(),
587
- server_version: '2.0.0'
588
- });
589
- });
590
- });
658
+ server.registerTool(
659
+ "wp_health_check",
660
+ {
661
+ title: "WordPress Health Check",
662
+ description: "Comprehensive health check of WordPress site and MCP server",
663
+ inputSchema: z.object({
664
+ detailed: z
665
+ .boolean()
666
+ .optional()
667
+ .default(false)
668
+ .describe("Include detailed health metrics"),
669
+ }),
670
+ },
671
+ async (params) => {
672
+ return executeTool("wp_health_check", params, async (params, sessionId) => {
673
+ const checks = [];
674
+
675
+ try {
676
+ // Test WordPress API connectivity
677
+ const startTime = Date.now();
678
+ const response = await wordpressRequest("/", "GET", null, sessionId);
679
+ checks.push({
680
+ name: "WordPress API Connectivity",
681
+ status: "passed",
682
+ response_time: Date.now() - startTime,
683
+ message: "API is responding",
684
+ });
685
+ } catch (error) {
686
+ checks.push({
687
+ name: "WordPress API Connectivity",
688
+ status: "failed",
689
+ error: error.message,
690
+ });
691
+ }
692
+
693
+ // Check authentication
694
+ try {
695
+ await wordpressRequest("/users/me", "GET", null, sessionId);
696
+ checks.push({
697
+ name: "Authentication",
698
+ status: "passed",
699
+ message: "Authentication working",
700
+ });
701
+ } catch (error) {
702
+ checks.push({
703
+ name: "Authentication",
704
+ status: "failed",
705
+ error: error.message,
706
+ });
707
+ }
708
+
709
+ // Session status
710
+ const activeSessionCount = sessionManager.sessions.size;
711
+ checks.push({
712
+ name: "Session Management",
713
+ status: "passed",
714
+ active_sessions: activeSessionCount,
715
+ current_session: sessionId,
716
+ });
717
+
718
+ // Memory status
719
+ const memoryEntries = memoryManager.list("wordpress");
720
+ checks.push({
721
+ name: "Memory Management",
722
+ status: "passed",
723
+ stored_entries: memoryEntries.length,
724
+ });
725
+
726
+ const overall = checks.every((check) => check.status === "passed");
727
+
728
+ return formatMCPResponse({
729
+ overall_status: overall ? "healthy" : "unhealthy",
730
+ checks: checks,
731
+ timestamp: Date.now(),
732
+ server_version: "2.0.0",
733
+ });
734
+ });
735
+ },
736
+ );
591
737
 
592
738
  // Get site information
593
- server.registerTool("wp_get_site_info", {
594
- title: "Get WordPress Site Information",
595
- description: "Get WordPress site statistics, version, and health information",
596
- inputSchema: {
597
- type: "object",
598
- properties: {
599
- include_health: {
600
- type: "boolean",
601
- description: "Include health metrics in response",
602
- default: false
603
- },
604
- cache_duration: {
605
- type: "number",
606
- description: "Cache duration in seconds",
607
- default: 300
608
- }
609
- },
610
- additionalProperties: false
611
- }
612
- }, async (params) => {
613
- return executeTool('wp_get_site_info', params, async (params, sessionId) => {
614
- // Check cache first
615
- const cacheKey = `site_info_${WORDPRESS_URL}`;
616
- const cached = memoryManager.retrieve(cacheKey, 'cache');
617
- if (cached && Date.now() - cached.timestamp < params.cache_duration * 1000) {
618
- return formatMCPResponse({
619
- ...cached.data,
620
- cached: true,
621
- cache_age: Math.floor((Date.now() - cached.timestamp) / 1000)
622
- });
623
- }
624
-
625
- // Get site info from multiple endpoints
626
- const [users, posts, plugins] = await Promise.all([
627
- wordpressRequest('/users', 'GET', null, sessionId),
628
- wordpressRequest('/posts?per_page=1', 'GET', null, sessionId),
629
- wordpressRequest('/plugins', 'GET', null, sessionId).catch(() => []) // Plugins endpoint might not be available
630
- ]);
631
-
632
- const siteInfo = {
633
- site_url: WORDPRESS_URL,
634
- total_users: users.length,
635
- total_posts: posts.length,
636
- total_plugins: plugins.length,
637
- wordpress_version: "Available via REST API",
638
- server_version: "2.0.0",
639
- last_updated: new Date().toISOString(),
640
- message: "Site information retrieved successfully"
641
- };
642
-
643
- // Store in cache
644
- memoryManager.store(cacheKey, {
645
- data: siteInfo,
646
- timestamp: Date.now()
647
- }, 'cache', params.cache_duration * 1000);
648
-
649
- if (params.include_health) {
650
- // Include basic health metrics
651
- siteInfo.health_metrics = {
652
- api_responsive: true,
653
- authenticated: true,
654
- last_check: Date.now()
655
- };
656
- }
657
-
658
- return formatMCPResponse(siteInfo);
659
- });
660
- });
739
+ server.registerTool(
740
+ "wp_get_site_info",
741
+ {
742
+ title: "Get WordPress Site Information",
743
+ description:
744
+ "Get WordPress site statistics, version, and health information",
745
+ inputSchema: z.object({
746
+ include_health: z
747
+ .boolean()
748
+ .optional()
749
+ .default(false)
750
+ .describe("Include health metrics in response"),
751
+ cache_duration: z
752
+ .number()
753
+ .optional()
754
+ .default(300)
755
+ .describe("Cache duration in seconds"),
756
+ }),
757
+ },
758
+ async (params) => {
759
+ return executeTool(
760
+ "wp_get_site_info",
761
+ params,
762
+ async (params, sessionId) => {
763
+ // Check cache first
764
+ const cacheKey = `site_info_${WORDPRESS_URL}`;
765
+ const cached = memoryManager.retrieve(cacheKey, "cache");
766
+ if (
767
+ cached &&
768
+ Date.now() - cached.timestamp < params.cache_duration * 1000
769
+ ) {
770
+ return formatMCPResponse({
771
+ ...cached.data,
772
+ cached: true,
773
+ cache_age: Math.floor((Date.now() - cached.timestamp) / 1000),
774
+ });
775
+ }
776
+
777
+ // Get site info from multiple endpoints
778
+ const [users, posts, plugins] = await Promise.all([
779
+ wordpressRequest("/users", "GET", null, sessionId),
780
+ wordpressRequest("/posts?per_page=1", "GET", null, sessionId),
781
+ wordpressRequest("/plugins", "GET", null, sessionId).catch(() => []), // Plugins endpoint might not be available
782
+ ]);
783
+
784
+ const siteInfo = {
785
+ site_url: WORDPRESS_URL,
786
+ total_users: users.length,
787
+ total_posts: posts.length,
788
+ total_plugins: plugins.length,
789
+ wordpress_version: "Available via REST API",
790
+ server_version: "2.0.0",
791
+ last_updated: new Date().toISOString(),
792
+ message: "Site information retrieved successfully",
793
+ };
794
+
795
+ // Store in cache
796
+ memoryManager.store(
797
+ cacheKey,
798
+ {
799
+ data: siteInfo,
800
+ timestamp: Date.now(),
801
+ },
802
+ "cache",
803
+ params.cache_duration * 1000,
804
+ );
805
+
806
+ if (params.include_health) {
807
+ // Include basic health metrics
808
+ siteInfo.health_metrics = {
809
+ api_responsive: true,
810
+ authenticated: true,
811
+ last_check: Date.now(),
812
+ };
813
+ }
814
+
815
+ return formatMCPResponse(siteInfo);
816
+ },
817
+ );
818
+ },
819
+ );
661
820
 
662
821
  // List posts
663
- server.registerTool("wp_list_posts", {
664
- title: "List WordPress Posts",
665
- description: "Get posts with filtering options",
666
- inputSchema: z.object({
667
- per_page: z.number().optional().default(10),
668
- page: z.number().optional().default(1),
669
- status: z.string().optional().default("publish"),
670
- search: z.string().optional(),
671
- categories: z.array(z.number()).optional(),
672
- tags: z.array(z.number()).optional()
673
- })
674
- }, async (params) => {
675
- try {
676
- const queryParams = new URLSearchParams();
677
-
678
- queryParams.append('per_page', params.per_page.toString());
679
- queryParams.append('page', params.page.toString());
680
- queryParams.append('status', params.status);
681
-
682
- if (params.search) queryParams.append('search', params.search);
683
- if (params.categories) queryParams.append('categories', params.categories.join(','));
684
- if (params.tags) queryParams.append('tags', params.tags.join(','));
685
-
686
- const posts = await wordpressRequest(`/posts?${queryParams.toString()}`);
687
-
688
- return {
689
- content: [{
690
- type: "text",
691
- text: JSON.stringify({
692
- success: true,
693
- posts: posts.map(post => ({
694
- id: post.id,
695
- title: post.title.rendered,
696
- status: post.status,
697
- date: post.date,
698
- link: post.link,
699
- excerpt: post.excerpt.rendered.replace(/<[^>]*>/g, '').trim()
700
- })),
701
- total: posts.length
702
- }, null, 2)
703
- }]
704
- };
705
- } catch (error) {
706
- return {
707
- content: [{
708
- type: "text",
709
- text: JSON.stringify({
710
- success: false,
711
- error: error.message
712
- }, null, 2)
713
- }]
714
- };
715
- }
716
- });
822
+ server.registerTool(
823
+ "wp_list_posts",
824
+ {
825
+ title: "List WordPress Posts",
826
+ description: "Get posts with filtering options",
827
+ inputSchema: z.object({
828
+ per_page: z.number().optional().default(10),
829
+ page: z.number().optional().default(1),
830
+ status: z.string().optional().default("publish"),
831
+ search: z.string().optional(),
832
+ categories: z.array(z.number()).optional(),
833
+ tags: z.array(z.number()).optional(),
834
+ }),
835
+ },
836
+ async (params) => {
837
+ try {
838
+ const queryParams = new URLSearchParams();
839
+
840
+ queryParams.append("per_page", params.per_page.toString());
841
+ queryParams.append("page", params.page.toString());
842
+ queryParams.append("status", params.status);
843
+
844
+ if (params.search) queryParams.append("search", params.search);
845
+ if (params.categories)
846
+ queryParams.append("categories", params.categories.join(","));
847
+ if (params.tags) queryParams.append("tags", params.tags.join(","));
848
+
849
+ const posts = await wordpressRequest(`/posts?${queryParams.toString()}`);
850
+
851
+ return {
852
+ content: [
853
+ {
854
+ type: "text",
855
+ text: JSON.stringify(
856
+ {
857
+ success: true,
858
+ posts: posts.map((post) => ({
859
+ id: post.id,
860
+ title: post.title.rendered,
861
+ status: post.status,
862
+ date: post.date,
863
+ link: post.link,
864
+ excerpt: post.excerpt.rendered.replace(/<[^>]*>/g, "").trim(),
865
+ })),
866
+ total: posts.length,
867
+ },
868
+ null,
869
+ 2,
870
+ ),
871
+ },
872
+ ],
873
+ };
874
+ } catch (error) {
875
+ return {
876
+ content: [
877
+ {
878
+ type: "text",
879
+ text: JSON.stringify(
880
+ {
881
+ success: false,
882
+ error: error.message,
883
+ },
884
+ null,
885
+ 2,
886
+ ),
887
+ },
888
+ ],
889
+ };
890
+ }
891
+ },
892
+ );
717
893
 
718
894
  // Create post
719
- server.registerTool("wp_create_post", {
720
- title: "Create WordPress Post",
721
- description: "Create a new WordPress post or page with enhanced tracking and validation",
722
- inputSchema: {
723
- type: "object",
724
- properties: {
725
- title: {
726
- type: "string",
727
- description: "Post title (required)",
728
- minLength: 1
729
- },
730
- content: {
731
- type: "string",
732
- description: "Post content (required)",
733
- minLength: 1
734
- },
735
- status: {
736
- type: "string",
737
- enum: ["draft", "publish", "private", "pending"],
738
- description: "Post status",
739
- default: "draft"
740
- },
741
- excerpt: {
742
- type: "string",
743
- description: "Post excerpt"
744
- },
745
- slug: {
746
- type: "string",
747
- description: "Post slug/URL"
748
- },
749
- categories: {
750
- type: "array",
751
- items: { type: "number" },
752
- description: "Category IDs",
753
- default: []
754
- },
755
- tags: {
756
- type: "array",
757
- items: { type: "number" },
758
- description: "Tag IDs",
759
- default: []
760
- },
761
- use_gutenberg: {
762
- type: "boolean",
763
- description: "Use Gutenberg block format",
764
- default: false
765
- },
766
- store_in_memory: {
767
- type: "boolean",
768
- description: "Store post data in memory for tracking",
769
- default: true
770
- }
771
- },
772
- required: ["title", "content"],
773
- additionalProperties: false
774
- }
775
- }, async (params) => {
776
- return executeTool('wp_create_post', params, async (params, sessionId) => {
777
- // Convert content to Gutenberg format if requested
778
- let content = params.content;
779
- const useGutenberg = params.use_gutenberg || params.gutenberg || WORDPRESS_USE_GUTENBERG;
780
-
781
- if (useGutenberg) {
782
- try {
783
- content = GutenbergConverter.htmlToGutenberg(params.content);
784
- } catch (gutenbergError) {
785
- // If Gutenberg conversion fails, log warning but continue with original content
786
- sessionManager.logOperation(sessionId, {
787
- type: 'warning',
788
- message: 'Gutenberg conversion failed, using original content',
789
- error: gutenbergError.message
790
- });
791
- }
792
- }
793
-
794
- const postData = {
795
- title: params.title,
796
- content: content,
797
- status: params.status,
798
- categories: params.categories,
799
- tags: params.tags
800
- };
801
-
802
- // Add optional fields
803
- if (params.excerpt) postData.excerpt = params.excerpt;
804
- if (params.slug) postData.slug = params.slug;
805
- if (params.meta) postData.meta = params.meta;
806
- if (params.featured_media) postData.featured_media = params.featured_media;
807
- if (params.author) postData.author = params.author;
808
- if (params.comment_status) postData.comment_status = params.comment_status;
809
- if (params.ping_status) postData.ping_status = params.ping_status;
810
-
811
- const post = await wordpressRequest('/posts', 'POST', postData, sessionId);
812
-
813
- const result = {
814
- post_id: post.id,
815
- post_url: post.link,
816
- edit_url: `${WORDPRESS_URL.replace(/\/$/, '')}/wp-admin/post.php?post=${post.id}&action=edit`,
817
- title: post.title.rendered,
818
- status: post.status,
819
- date: post.date,
820
- modified: post.modified,
821
- gutenberg_used: useGutenberg,
822
- categories: post.categories,
823
- tags: post.tags,
824
- message: `Post created successfully with ${useGutenberg ? 'Gutenberg' : 'Classic'} editor format`
825
- };
826
-
827
- // Store post reference in memory if requested
828
- if (params.store_in_memory) {
829
- memoryManager.store(`created_post_${post.id}`, {
830
- ...result,
831
- original_params: params,
832
- created_at: Date.now()
833
- }, 'posts', 3600000); // 1 hour TTL
834
- }
835
-
836
- return formatMCPResponse(result);
837
- });
838
- });
895
+ server.registerTool(
896
+ "wp_create_post",
897
+ {
898
+ title: "Create WordPress Post",
899
+ description:
900
+ "Create a new WordPress post or page with enhanced tracking and validation",
901
+ inputSchema: z.object({
902
+ title: z.string().min(1).describe("Post title (required)"),
903
+ content: z.string().min(1).describe("Post content (required)"),
904
+ status: z
905
+ .enum(["draft", "publish", "private", "pending"])
906
+ .optional()
907
+ .default("draft")
908
+ .describe("Post status"),
909
+ excerpt: z.string().optional().describe("Post excerpt"),
910
+ slug: z.string().optional().describe("Post slug/URL"),
911
+ categories: z
912
+ .array(z.number())
913
+ .optional()
914
+ .default([])
915
+ .describe("Category IDs"),
916
+ tags: z.array(z.number()).optional().default([]).describe("Tag IDs"),
917
+ use_gutenberg: z
918
+ .boolean()
919
+ .optional()
920
+ .default(false)
921
+ .describe("Use Gutenberg block format"),
922
+ store_in_memory: z
923
+ .boolean()
924
+ .optional()
925
+ .default(true)
926
+ .describe("Store post data in memory for tracking"),
927
+ content_base_path: z
928
+ .string()
929
+ .optional()
930
+ .describe(
931
+ "Base directory path to resolve relative asset references (e.g. assets/image.svg) in content. When provided, local assets are uploaded to the media library and URLs are replaced.",
932
+ ),
933
+ }),
934
+ },
935
+ async (params) => {
936
+ return executeTool("wp_create_post", params, async (params, sessionId) => {
937
+ // Upload local assets if content_base_path is provided
938
+ let content = params.content;
939
+ let uploadedAssets = [];
940
+ if (params.content_base_path) {
941
+ const assetResult = await uploadAndReplaceAssets(
942
+ content,
943
+ params.content_base_path,
944
+ sessionId,
945
+ );
946
+ content = assetResult.content;
947
+ uploadedAssets = assetResult.uploadedAssets;
948
+ }
949
+
950
+ // Convert content to Gutenberg format if requested
951
+ const useGutenberg =
952
+ params.use_gutenberg || params.gutenberg || WORDPRESS_USE_GUTENBERG;
953
+
954
+ if (useGutenberg) {
955
+ try {
956
+ content = GutenbergConverter.toGutenberg(content);
957
+ } catch (gutenbergError) {
958
+ // If Gutenberg conversion fails, log warning but continue with original content
959
+ sessionManager.logOperation(sessionId, {
960
+ type: "warning",
961
+ message: "Gutenberg conversion failed, using original content",
962
+ error: gutenbergError.message,
963
+ });
964
+ }
965
+ }
966
+
967
+ const postData = {
968
+ title: params.title,
969
+ content: content,
970
+ status: params.status,
971
+ categories: params.categories,
972
+ tags: params.tags,
973
+ };
974
+
975
+ // Add optional fields
976
+ if (params.excerpt) postData.excerpt = params.excerpt;
977
+ if (params.slug) postData.slug = params.slug;
978
+ if (params.meta) postData.meta = params.meta;
979
+ if (params.featured_media)
980
+ postData.featured_media = params.featured_media;
981
+ if (params.author) postData.author = params.author;
982
+ if (params.comment_status)
983
+ postData.comment_status = params.comment_status;
984
+ if (params.ping_status) postData.ping_status = params.ping_status;
985
+
986
+ const post = await wordpressRequest(
987
+ "/posts",
988
+ "POST",
989
+ postData,
990
+ sessionId,
991
+ );
992
+
993
+ const result = {
994
+ post_id: post.id,
995
+ post_url: post.link,
996
+ edit_url: `${WORDPRESS_URL.replace(/\/$/, "")}/wp-admin/post.php?post=${post.id}&action=edit`,
997
+ title: post.title.rendered,
998
+ status: post.status,
999
+ date: post.date,
1000
+ modified: post.modified,
1001
+ gutenberg_used: useGutenberg,
1002
+ categories: post.categories,
1003
+ tags: post.tags,
1004
+ uploaded_assets: uploadedAssets.length > 0 ? uploadedAssets : undefined,
1005
+ message: `Post created successfully with ${useGutenberg ? "Gutenberg" : "Classic"} editor format`,
1006
+ };
1007
+
1008
+ // Store post reference in memory if requested
1009
+ if (params.store_in_memory) {
1010
+ memoryManager.store(
1011
+ `created_post_${post.id}`,
1012
+ {
1013
+ ...result,
1014
+ original_params: params,
1015
+ created_at: Date.now(),
1016
+ },
1017
+ "posts",
1018
+ 3600000,
1019
+ ); // 1 hour TTL
1020
+ }
1021
+
1022
+ return formatMCPResponse(result);
1023
+ });
1024
+ },
1025
+ );
839
1026
 
840
1027
  // Update post
841
- server.registerTool("wp_update_post", {
842
- title: "Update WordPress Post",
843
- description: "Update an existing WordPress post",
844
- inputSchema: z.object({
845
- id: z.number(),
846
- title: z.string().optional(),
847
- content: z.string().optional(),
848
- status: z.string().optional(),
849
- excerpt: z.string().optional(),
850
- slug: z.string().optional(),
851
- categories: z.array(z.number()).optional(),
852
- tags: z.array(z.number()).optional()
853
- })
854
- }, async (params) => {
855
- try {
856
- const { id, ...updateData } = params;
857
- const filteredData = Object.fromEntries(
858
- Object.entries(updateData).filter(([_, value]) => value !== undefined)
859
- );
860
-
861
- const post = await wordpressRequest(`/posts/${id}`, 'POST', filteredData);
862
-
863
- return {
864
- content: [{
865
- type: "text",
866
- text: JSON.stringify({
867
- success: true,
868
- post_id: post.id,
869
- post_url: post.link,
870
- message: "Post updated successfully"
871
- }, null, 2)
872
- }]
873
- };
874
- } catch (error) {
875
- return {
876
- content: [{
877
- type: "text",
878
- text: JSON.stringify({
879
- success: false,
880
- error: error.message
881
- }, null, 2)
882
- }]
883
- };
884
- }
885
- });
1028
+ server.registerTool(
1029
+ "wp_update_post",
1030
+ {
1031
+ title: "Update WordPress Post",
1032
+ description: "Update an existing WordPress post",
1033
+ inputSchema: z.object({
1034
+ id: z.number(),
1035
+ title: z.string().optional(),
1036
+ content: z.string().optional(),
1037
+ status: z.string().optional(),
1038
+ excerpt: z.string().optional(),
1039
+ slug: z.string().optional(),
1040
+ categories: z.array(z.number()).optional(),
1041
+ tags: z.array(z.number()).optional(),
1042
+ }),
1043
+ },
1044
+ async (params) => {
1045
+ try {
1046
+ const { id, ...updateData } = params;
1047
+ const filteredData = Object.fromEntries(
1048
+ Object.entries(updateData).filter(([_, value]) => value !== undefined),
1049
+ );
1050
+
1051
+ const post = await wordpressRequest(`/posts/${id}`, "POST", filteredData);
1052
+
1053
+ return {
1054
+ content: [
1055
+ {
1056
+ type: "text",
1057
+ text: JSON.stringify(
1058
+ {
1059
+ success: true,
1060
+ post_id: post.id,
1061
+ post_url: post.link,
1062
+ message: "Post updated successfully",
1063
+ },
1064
+ null,
1065
+ 2,
1066
+ ),
1067
+ },
1068
+ ],
1069
+ };
1070
+ } catch (error) {
1071
+ return {
1072
+ content: [
1073
+ {
1074
+ type: "text",
1075
+ text: JSON.stringify(
1076
+ {
1077
+ success: false,
1078
+ error: error.message,
1079
+ },
1080
+ null,
1081
+ 2,
1082
+ ),
1083
+ },
1084
+ ],
1085
+ };
1086
+ }
1087
+ },
1088
+ );
886
1089
 
887
1090
  // Delete post
888
- server.registerTool("wp_delete_post", {
889
- title: "Delete WordPress Post",
890
- description: "Delete a WordPress post (move to trash)",
891
- inputSchema: z.object({
892
- id: z.number()
893
- })
894
- }, async (params) => {
895
- try {
896
- const post = await wordpressRequest(`/posts/${params.id}`, 'DELETE');
897
-
898
- return {
899
- content: [{
900
- type: "text",
901
- text: JSON.stringify({
902
- success: true,
903
- post_id: post.id,
904
- message: "Post deleted successfully"
905
- }, null, 2)
906
- }]
907
- };
908
- } catch (error) {
909
- return {
910
- content: [{
911
- type: "text",
912
- text: JSON.stringify({
913
- success: false,
914
- error: error.message
915
- }, null, 2)
916
- }]
917
- };
918
- }
919
- });
1091
+ server.registerTool(
1092
+ "wp_delete_post",
1093
+ {
1094
+ title: "Delete WordPress Post",
1095
+ description: "Delete a WordPress post (move to trash)",
1096
+ inputSchema: z.object({
1097
+ id: z.number(),
1098
+ }),
1099
+ },
1100
+ async (params) => {
1101
+ try {
1102
+ const post = await wordpressRequest(`/posts/${params.id}`, "DELETE");
1103
+
1104
+ return {
1105
+ content: [
1106
+ {
1107
+ type: "text",
1108
+ text: JSON.stringify(
1109
+ {
1110
+ success: true,
1111
+ post_id: post.id,
1112
+ message: "Post deleted successfully",
1113
+ },
1114
+ null,
1115
+ 2,
1116
+ ),
1117
+ },
1118
+ ],
1119
+ };
1120
+ } catch (error) {
1121
+ return {
1122
+ content: [
1123
+ {
1124
+ type: "text",
1125
+ text: JSON.stringify(
1126
+ {
1127
+ success: false,
1128
+ error: error.message,
1129
+ },
1130
+ null,
1131
+ 2,
1132
+ ),
1133
+ },
1134
+ ],
1135
+ };
1136
+ }
1137
+ },
1138
+ );
920
1139
 
921
1140
  // List users
922
- server.registerTool("wp_list_users", {
923
- title: "List WordPress Users",
924
- description: "Get WordPress user accounts and roles",
925
- inputSchema: z.object({
926
- per_page: z.number().optional().default(100),
927
- role: z.string().optional()
928
- })
929
- }, async (params) => {
930
- try {
931
- const queryParams = new URLSearchParams();
932
- queryParams.append('per_page', params.per_page.toString());
933
- if (params.role) queryParams.append('role', params.role);
934
-
935
- const users = await wordpressRequest(`/users?${queryParams.toString()}`);
936
-
937
- return {
938
- content: [{
939
- type: "text",
940
- text: JSON.stringify({
941
- success: true,
942
- users: users.map(user => ({
943
- id: user.id,
944
- username: user.username,
945
- name: user.name,
946
- email: user.email,
947
- roles: user.roles,
948
- registered_date: user.registered_date
949
- })),
950
- total: users.length
951
- }, null, 2)
952
- }]
953
- };
954
- } catch (error) {
955
- return {
956
- content: [{
957
- type: "text",
958
- text: JSON.stringify({
959
- success: false,
960
- error: error.message
961
- }, null, 2)
962
- }]
963
- };
964
- }
965
- });
1141
+ server.registerTool(
1142
+ "wp_list_users",
1143
+ {
1144
+ title: "List WordPress Users",
1145
+ description: "Get WordPress user accounts and roles",
1146
+ inputSchema: z.object({
1147
+ per_page: z.number().optional().default(100),
1148
+ role: z.string().optional(),
1149
+ }),
1150
+ },
1151
+ async (params) => {
1152
+ try {
1153
+ const queryParams = new URLSearchParams();
1154
+ queryParams.append("per_page", params.per_page.toString());
1155
+ if (params.role) queryParams.append("role", params.role);
1156
+
1157
+ const users = await wordpressRequest(`/users?${queryParams.toString()}`);
1158
+
1159
+ return {
1160
+ content: [
1161
+ {
1162
+ type: "text",
1163
+ text: JSON.stringify(
1164
+ {
1165
+ success: true,
1166
+ users: users.map((user) => ({
1167
+ id: user.id,
1168
+ username: user.username,
1169
+ name: user.name,
1170
+ email: user.email,
1171
+ roles: user.roles,
1172
+ registered_date: user.registered_date,
1173
+ })),
1174
+ total: users.length,
1175
+ },
1176
+ null,
1177
+ 2,
1178
+ ),
1179
+ },
1180
+ ],
1181
+ };
1182
+ } catch (error) {
1183
+ return {
1184
+ content: [
1185
+ {
1186
+ type: "text",
1187
+ text: JSON.stringify(
1188
+ {
1189
+ success: false,
1190
+ error: error.message,
1191
+ },
1192
+ null,
1193
+ 2,
1194
+ ),
1195
+ },
1196
+ ],
1197
+ };
1198
+ }
1199
+ },
1200
+ );
966
1201
 
967
1202
  // Create user
968
- server.registerTool("wp_create_user", {
969
- title: "Create WordPress User",
970
- description: "Add a new WordPress user account",
971
- inputSchema: z.object({
972
- username: z.string(),
973
- email: z.string().email(),
974
- password: z.string(),
975
- roles: z.array(z.string()).optional().default(["subscriber"])
976
- })
977
- }, async (params) => {
978
- try {
979
- const userData = {
980
- username: params.username,
981
- email: params.email,
982
- password: params.password,
983
- roles: params.roles
984
- };
985
-
986
- const user = await wordpressRequest('/users', 'POST', userData);
987
-
988
- return {
989
- content: [{
990
- type: "text",
991
- text: JSON.stringify({
992
- success: true,
993
- user_id: user.id,
994
- username: user.username,
995
- email: user.email,
996
- roles: user.roles,
997
- message: "User created successfully"
998
- }, null, 2)
999
- }]
1000
- };
1001
- } catch (error) {
1002
- return {
1003
- content: [{
1004
- type: "text",
1005
- text: JSON.stringify({
1006
- success: false,
1007
- error: error.message
1008
- }, null, 2)
1009
- }]
1010
- };
1011
- }
1012
- });
1203
+ server.registerTool(
1204
+ "wp_create_user",
1205
+ {
1206
+ title: "Create WordPress User",
1207
+ description: "Add a new WordPress user account",
1208
+ inputSchema: z.object({
1209
+ username: z.string(),
1210
+ email: z.string().email(),
1211
+ password: z.string(),
1212
+ roles: z.array(z.string()).optional().default(["subscriber"]),
1213
+ }),
1214
+ },
1215
+ async (params) => {
1216
+ try {
1217
+ const userData = {
1218
+ username: params.username,
1219
+ email: params.email,
1220
+ password: params.password,
1221
+ roles: params.roles,
1222
+ };
1223
+
1224
+ const user = await wordpressRequest("/users", "POST", userData);
1225
+
1226
+ return {
1227
+ content: [
1228
+ {
1229
+ type: "text",
1230
+ text: JSON.stringify(
1231
+ {
1232
+ success: true,
1233
+ user_id: user.id,
1234
+ username: user.username,
1235
+ email: user.email,
1236
+ roles: user.roles,
1237
+ message: "User created successfully",
1238
+ },
1239
+ null,
1240
+ 2,
1241
+ ),
1242
+ },
1243
+ ],
1244
+ };
1245
+ } catch (error) {
1246
+ return {
1247
+ content: [
1248
+ {
1249
+ type: "text",
1250
+ text: JSON.stringify(
1251
+ {
1252
+ success: false,
1253
+ error: error.message,
1254
+ },
1255
+ null,
1256
+ 2,
1257
+ ),
1258
+ },
1259
+ ],
1260
+ };
1261
+ }
1262
+ },
1263
+ );
1013
1264
 
1014
1265
  // Update user
1015
- server.registerTool("wp_update_user", {
1016
- title: "Update WordPress User",
1017
- description: "Modify WordPress user settings",
1018
- inputSchema: z.object({
1019
- id: z.number(),
1020
- email: z.string().email().optional(),
1021
- roles: z.array(z.string()).optional(),
1022
- password: z.string().optional()
1023
- })
1024
- }, async (params) => {
1025
- try {
1026
- const { id, ...updateData } = params;
1027
- const filteredData = Object.fromEntries(
1028
- Object.entries(updateData).filter(([_, value]) => value !== undefined)
1029
- );
1030
-
1031
- const user = await wordpressRequest(`/users/${id}`, 'POST', filteredData);
1032
-
1033
- return {
1034
- content: [{
1035
- type: "text",
1036
- text: JSON.stringify({
1037
- success: true,
1038
- user_id: user.id,
1039
- username: user.username,
1040
- email: user.email,
1041
- roles: user.roles,
1042
- message: "User updated successfully"
1043
- }, null, 2)
1044
- }]
1045
- };
1046
- } catch (error) {
1047
- return {
1048
- content: [{
1049
- type: "text",
1050
- text: JSON.stringify({
1051
- success: false,
1052
- error: error.message
1053
- }, null, 2)
1054
- }]
1055
- };
1056
- }
1057
- });
1266
+ server.registerTool(
1267
+ "wp_update_user",
1268
+ {
1269
+ title: "Update WordPress User",
1270
+ description: "Modify WordPress user settings",
1271
+ inputSchema: z.object({
1272
+ id: z.number(),
1273
+ email: z.string().email().optional(),
1274
+ roles: z.array(z.string()).optional(),
1275
+ password: z.string().optional(),
1276
+ }),
1277
+ },
1278
+ async (params) => {
1279
+ try {
1280
+ const { id, ...updateData } = params;
1281
+ const filteredData = Object.fromEntries(
1282
+ Object.entries(updateData).filter(([_, value]) => value !== undefined),
1283
+ );
1284
+
1285
+ const user = await wordpressRequest(`/users/${id}`, "POST", filteredData);
1286
+
1287
+ return {
1288
+ content: [
1289
+ {
1290
+ type: "text",
1291
+ text: JSON.stringify(
1292
+ {
1293
+ success: true,
1294
+ user_id: user.id,
1295
+ username: user.username,
1296
+ email: user.email,
1297
+ roles: user.roles,
1298
+ message: "User updated successfully",
1299
+ },
1300
+ null,
1301
+ 2,
1302
+ ),
1303
+ },
1304
+ ],
1305
+ };
1306
+ } catch (error) {
1307
+ return {
1308
+ content: [
1309
+ {
1310
+ type: "text",
1311
+ text: JSON.stringify(
1312
+ {
1313
+ success: false,
1314
+ error: error.message,
1315
+ },
1316
+ null,
1317
+ 2,
1318
+ ),
1319
+ },
1320
+ ],
1321
+ };
1322
+ }
1323
+ },
1324
+ );
1058
1325
 
1059
1326
  // List plugins (if available)
1060
- server.registerTool("wp_list_plugins", {
1061
- title: "List WordPress Plugins",
1062
- description: "Get installed WordPress plugins",
1063
- inputSchema: z.object({})
1064
- }, async () => {
1065
- try {
1066
- const plugins = await wordpressRequest('/plugins');
1067
-
1068
- return {
1069
- content: [{
1070
- type: "text",
1071
- text: JSON.stringify({
1072
- success: true,
1073
- plugins: plugins.map(plugin => ({
1074
- name: plugin.name,
1075
- plugin: plugin.plugin,
1076
- status: plugin.status,
1077
- version: plugin.version
1078
- })),
1079
- total: plugins.length
1080
- }, null, 2)
1081
- }]
1082
- };
1083
- } catch (error) {
1084
- return {
1085
- content: [{
1086
- type: "text",
1087
- text: JSON.stringify({
1088
- success: false,
1089
- error: error.message,
1090
- note: "Plugins endpoint may not be available on all WordPress installations"
1091
- }, null, 2)
1092
- }]
1093
- };
1094
- }
1095
- });
1327
+ server.registerTool(
1328
+ "wp_list_plugins",
1329
+ {
1330
+ title: "List WordPress Plugins",
1331
+ description: "Get installed WordPress plugins",
1332
+ inputSchema: z.object({}),
1333
+ },
1334
+ async () => {
1335
+ try {
1336
+ const plugins = await wordpressRequest("/plugins");
1337
+
1338
+ return {
1339
+ content: [
1340
+ {
1341
+ type: "text",
1342
+ text: JSON.stringify(
1343
+ {
1344
+ success: true,
1345
+ plugins: plugins.map((plugin) => ({
1346
+ name: plugin.name,
1347
+ plugin: plugin.plugin,
1348
+ status: plugin.status,
1349
+ version: plugin.version,
1350
+ })),
1351
+ total: plugins.length,
1352
+ },
1353
+ null,
1354
+ 2,
1355
+ ),
1356
+ },
1357
+ ],
1358
+ };
1359
+ } catch (error) {
1360
+ return {
1361
+ content: [
1362
+ {
1363
+ type: "text",
1364
+ text: JSON.stringify(
1365
+ {
1366
+ success: false,
1367
+ error: error.message,
1368
+ note: "Plugins endpoint may not be available on all WordPress installations",
1369
+ },
1370
+ null,
1371
+ 2,
1372
+ ),
1373
+ },
1374
+ ],
1375
+ };
1376
+ }
1377
+ },
1378
+ );
1096
1379
 
1097
1380
  // Get media
1098
- server.registerTool("wp_get_media", {
1099
- title: "Get WordPress Media",
1100
- description: "Access WordPress media library",
1101
- inputSchema: z.object({
1102
- per_page: z.number().optional().default(10),
1103
- media_type: z.string().optional()
1104
- })
1105
- }, async (params) => {
1106
- try {
1107
- const queryParams = new URLSearchParams();
1108
- queryParams.append('per_page', params.per_page.toString());
1109
- if (params.media_type) queryParams.append('media_type', params.media_type);
1110
-
1111
- const media = await wordpressRequest(`/media?${queryParams.toString()}`);
1112
-
1113
- return {
1114
- content: [{
1115
- type: "text",
1116
- text: JSON.stringify({
1117
- success: true,
1118
- media: media.map(item => ({
1119
- id: item.id,
1120
- title: item.title.rendered,
1121
- source_url: item.source_url,
1122
- media_type: item.media_type,
1123
- mime_type: item.mime_type,
1124
- date: item.date
1125
- })),
1126
- total: media.length
1127
- }, null, 2)
1128
- }]
1129
- };
1130
- } catch (error) {
1131
- return {
1132
- content: [{
1133
- type: "text",
1134
- text: JSON.stringify({
1135
- success: false,
1136
- error: error.message
1137
- }, null, 2)
1138
- }]
1139
- };
1140
- }
1141
- });
1381
+ server.registerTool(
1382
+ "wp_get_media",
1383
+ {
1384
+ title: "Get WordPress Media",
1385
+ description: "Access WordPress media library",
1386
+ inputSchema: z.object({
1387
+ per_page: z.number().optional().default(10),
1388
+ media_type: z.string().optional(),
1389
+ }),
1390
+ },
1391
+ async (params) => {
1392
+ try {
1393
+ const queryParams = new URLSearchParams();
1394
+ queryParams.append("per_page", params.per_page.toString());
1395
+ if (params.media_type)
1396
+ queryParams.append("media_type", params.media_type);
1397
+
1398
+ const media = await wordpressRequest(`/media?${queryParams.toString()}`);
1399
+
1400
+ return {
1401
+ content: [
1402
+ {
1403
+ type: "text",
1404
+ text: JSON.stringify(
1405
+ {
1406
+ success: true,
1407
+ media: media.map((item) => ({
1408
+ id: item.id,
1409
+ title: item.title.rendered,
1410
+ source_url: item.source_url,
1411
+ media_type: item.media_type,
1412
+ mime_type: item.mime_type,
1413
+ date: item.date,
1414
+ })),
1415
+ total: media.length,
1416
+ },
1417
+ null,
1418
+ 2,
1419
+ ),
1420
+ },
1421
+ ],
1422
+ };
1423
+ } catch (error) {
1424
+ return {
1425
+ content: [
1426
+ {
1427
+ type: "text",
1428
+ text: JSON.stringify(
1429
+ {
1430
+ success: false,
1431
+ error: error.message,
1432
+ },
1433
+ null,
1434
+ 2,
1435
+ ),
1436
+ },
1437
+ ],
1438
+ };
1439
+ }
1440
+ },
1441
+ );
1142
1442
 
1143
1443
  // Batch operations tool
1144
- server.registerTool("wp_batch_publish", {
1145
- title: "WordPress Batch Publish",
1146
- description: "Publish multiple posts from markdown files with enhanced tracking",
1147
- inputSchema: z.object({
1148
- files: z.array(z.object({
1149
- path: z.string(),
1150
- title: z.string().optional(),
1151
- status: z.enum(["draft", "publish", "private", "pending"]).optional().default("draft"),
1152
- categories: z.array(z.number()).optional().default([]),
1153
- tags: z.array(z.number()).optional().default([])
1154
- })),
1155
- use_gutenberg: z.boolean().optional().default(WORDPRESS_USE_GUTENBERG),
1156
- dry_run: z.boolean().optional().default(false)
1157
- })
1158
- }, async (params) => {
1159
- return executeTool('wp_batch_publish', params, async (params, sessionId) => {
1160
- const results = [];
1161
- const errors = [];
1162
-
1163
- for (const file of params.files) {
1164
- try {
1165
- if (params.dry_run) {
1166
- results.push({
1167
- file: file.path,
1168
- status: 'dry_run',
1169
- message: 'Would process this file'
1170
- });
1171
- continue;
1172
- }
1173
-
1174
- const postData = {
1175
- title: file.title || `Post from ${file.path}`,
1176
- content: `Content from ${file.path}`,
1177
- status: file.status,
1178
- categories: file.categories,
1179
- tags: file.tags,
1180
- use_gutenberg: params.use_gutenberg,
1181
- store_in_memory: true
1182
- };
1183
-
1184
- const post = await wordpressRequest('/posts', 'POST', postData, sessionId);
1185
-
1186
- results.push({
1187
- file: file.path,
1188
- post_id: post.id,
1189
- post_url: post.link,
1190
- status: 'success',
1191
- message: 'Published successfully'
1192
- });
1193
-
1194
- } catch (error) {
1195
- errors.push({
1196
- file: file.path,
1197
- error: error.message,
1198
- status: 'error'
1199
- });
1200
- }
1201
- }
1202
-
1203
- // Store batch operation result in memory
1204
- const batchId = `batch_${Date.now()}`;
1205
- memoryManager.store(batchId, {
1206
- operation: 'batch_publish',
1207
- results,
1208
- errors,
1209
- total_files: params.files.length,
1210
- successful: results.filter(r => r.status === 'success').length,
1211
- failed: errors.length,
1212
- completed_at: Date.now()
1213
- }, 'batch_operations', 3600000); // 1 hour TTL
1214
-
1215
- return formatMCPResponse({
1216
- batch_id: batchId,
1217
- summary: {
1218
- total_files: params.files.length,
1219
- successful: results.filter(r => r.status === 'success').length,
1220
- failed: errors.length,
1221
- dry_run: params.dry_run
1222
- },
1223
- results,
1224
- errors
1225
- });
1226
- });
1227
- });
1444
+ server.registerTool(
1445
+ "wp_batch_publish",
1446
+ {
1447
+ title: "WordPress Batch Publish",
1448
+ description:
1449
+ "Publish multiple posts from markdown files with enhanced tracking",
1450
+ inputSchema: z.object({
1451
+ files: z.array(
1452
+ z.object({
1453
+ path: z.string(),
1454
+ title: z.string().optional(),
1455
+ status: z
1456
+ .enum(["draft", "publish", "private", "pending"])
1457
+ .optional()
1458
+ .default("draft"),
1459
+ categories: z.array(z.number()).optional().default([]),
1460
+ tags: z.array(z.number()).optional().default([]),
1461
+ }),
1462
+ ),
1463
+ use_gutenberg: z.boolean().optional().default(WORDPRESS_USE_GUTENBERG),
1464
+ upload_assets: z
1465
+ .boolean()
1466
+ .optional()
1467
+ .default(true)
1468
+ .describe(
1469
+ "Upload local assets referenced in content (e.g. assets/image.svg) to the media library",
1470
+ ),
1471
+ dry_run: z.boolean().optional().default(false),
1472
+ }),
1473
+ },
1474
+ async (params) => {
1475
+ return executeTool(
1476
+ "wp_batch_publish",
1477
+ params,
1478
+ async (params, sessionId) => {
1479
+ const results = [];
1480
+ const errors = [];
1481
+
1482
+ for (const file of params.files) {
1483
+ try {
1484
+ if (params.dry_run) {
1485
+ results.push({
1486
+ file: file.path,
1487
+ status: "dry_run",
1488
+ message: "Would process this file",
1489
+ });
1490
+ continue;
1491
+ }
1492
+
1493
+ // Read actual file content
1494
+ let fileContent;
1495
+ try {
1496
+ fileContent = fs.readFileSync(file.path, "utf-8");
1497
+ } catch {
1498
+ errors.push({
1499
+ file: file.path,
1500
+ error: `Could not read file: ${file.path}`,
1501
+ status: "error",
1502
+ });
1503
+ continue;
1504
+ }
1505
+
1506
+ // Upload local assets if enabled
1507
+ let uploadedAssets = [];
1508
+ if (params.upload_assets) {
1509
+ const basePath = path.dirname(file.path);
1510
+ const assetResult = await uploadAndReplaceAssets(
1511
+ fileContent,
1512
+ basePath,
1513
+ sessionId,
1514
+ );
1515
+ fileContent = assetResult.content;
1516
+ uploadedAssets = assetResult.uploadedAssets;
1517
+ }
1518
+
1519
+ // Convert content to Gutenberg format if requested
1520
+ const useGutenberg =
1521
+ params.use_gutenberg || WORDPRESS_USE_GUTENBERG;
1522
+ if (useGutenberg) {
1523
+ try {
1524
+ fileContent = GutenbergConverter.toGutenberg(fileContent);
1525
+ } catch (gutenbergError) {
1526
+ sessionManager.logOperation(sessionId, {
1527
+ type: "warning",
1528
+ message: `Gutenberg conversion failed for ${file.path}, using original content`,
1529
+ error: gutenbergError.message,
1530
+ });
1531
+ }
1532
+ }
1533
+
1534
+ const postData = {
1535
+ title: file.title || `Post from ${file.path}`,
1536
+ content: fileContent,
1537
+ status: file.status,
1538
+ categories: file.categories,
1539
+ tags: file.tags,
1540
+ store_in_memory: true,
1541
+ };
1542
+
1543
+ const post = await wordpressRequest(
1544
+ "/posts",
1545
+ "POST",
1546
+ postData,
1547
+ sessionId,
1548
+ );
1549
+
1550
+ results.push({
1551
+ file: file.path,
1552
+ post_id: post.id,
1553
+ post_url: post.link,
1554
+ status: "success",
1555
+ uploaded_assets:
1556
+ uploadedAssets.length > 0 ? uploadedAssets : undefined,
1557
+ message: "Published successfully",
1558
+ });
1559
+ } catch (error) {
1560
+ errors.push({
1561
+ file: file.path,
1562
+ error: error.message,
1563
+ status: "error",
1564
+ });
1565
+ }
1566
+ }
1567
+
1568
+ // Store batch operation result in memory
1569
+ const batchId = `batch_${Date.now()}`;
1570
+ memoryManager.store(
1571
+ batchId,
1572
+ {
1573
+ operation: "batch_publish",
1574
+ results,
1575
+ errors,
1576
+ total_files: params.files.length,
1577
+ successful: results.filter((r) => r.status === "success").length,
1578
+ failed: errors.length,
1579
+ completed_at: Date.now(),
1580
+ },
1581
+ "batch_operations",
1582
+ 3600000,
1583
+ ); // 1 hour TTL
1584
+
1585
+ return formatMCPResponse({
1586
+ batch_id: batchId,
1587
+ summary: {
1588
+ total_files: params.files.length,
1589
+ successful: results.filter((r) => r.status === "success").length,
1590
+ failed: errors.length,
1591
+ dry_run: params.dry_run,
1592
+ },
1593
+ results,
1594
+ errors,
1595
+ });
1596
+ },
1597
+ );
1598
+ },
1599
+ );
1228
1600
 
1229
1601
  // Resource cleanup and server lifecycle management
1230
1602
  function setupCleanupHandlers() {
1231
- // Cleanup sessions every 10 minutes
1232
- setInterval(() => {
1233
- sessionManager.cleanup();
1234
- }, 10 * 60 * 1000);
1235
-
1236
- // Handle graceful shutdown
1237
- const cleanup = () => {
1238
- console.error("WordPress MCP Server shutting down...");
1239
- process.exit(0);
1240
- };
1241
-
1242
- process.on('SIGINT', cleanup);
1243
- process.on('SIGTERM', cleanup);
1244
- process.on('SIGHUP', cleanup);
1603
+ // Cleanup sessions every 10 minutes
1604
+ setInterval(
1605
+ () => {
1606
+ sessionManager.cleanup();
1607
+ },
1608
+ 10 * 60 * 1000,
1609
+ );
1610
+
1611
+ // Handle graceful shutdown
1612
+ const cleanup = () => {
1613
+ console.error("WordPress MCP Server shutting down...");
1614
+ process.exit(0);
1615
+ };
1616
+
1617
+ process.on("SIGINT", cleanup);
1618
+ process.on("SIGTERM", cleanup);
1619
+ process.on("SIGHUP", cleanup);
1245
1620
  }
1246
1621
 
1247
1622
  // Start the enhanced MCP server
1248
1623
  async function main() {
1249
- try {
1250
- // Initialize session manager
1251
- sessionManager.createSession('default_session');
1252
-
1253
- // Setup cleanup handlers
1254
- setupCleanupHandlers();
1255
-
1256
- const transport = new StdioServerTransport();
1257
- await server.connect(transport);
1258
-
1259
- console.error("Enhanced WordPress MCP Server v2.0.0 running...");
1260
- console.error(`Connected to WordPress site: ${WORDPRESS_URL}`);
1261
- console.error(`Active session: ${sessionManager.activeSession}`);
1262
- console.error("Available tools: session management, memory management, health checks, WordPress operations, batch publishing");
1263
-
1264
- } catch (error) {
1265
- console.error("Failed to start server:", error.message);
1266
- process.exit(1);
1267
- }
1624
+ try {
1625
+ // Initialize session manager
1626
+ sessionManager.createSession("default_session");
1627
+
1628
+ // Setup cleanup handlers
1629
+ setupCleanupHandlers();
1630
+
1631
+ const transport = new StdioServerTransport();
1632
+ await server.connect(transport);
1633
+
1634
+ console.error("Enhanced WordPress MCP Server v2.0.0 running...");
1635
+ console.error(`Connected to WordPress site: ${WORDPRESS_URL}`);
1636
+ console.error(`Active session: ${sessionManager.activeSession}`);
1637
+ console.error(
1638
+ "Available tools: session management, memory management, health checks, WordPress operations, batch publishing",
1639
+ );
1640
+ } catch (error) {
1641
+ console.error("Failed to start server:", error.message);
1642
+ process.exit(1);
1643
+ }
1268
1644
  }
1269
1645
 
1270
1646
  main().catch((error) => {
1271
- console.error("Server startup error:", error);
1272
- process.exit(1);
1647
+ console.error("Server startup error:", error);
1648
+ process.exit(1);
1273
1649
  });