myaidev-method 0.3.2 → 0.3.4

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 (80) hide show
  1. package/.claude-plugin/plugin.json +52 -48
  2. package/DEV_WORKFLOW_GUIDE.md +6 -6
  3. package/MCP_INTEGRATION.md +4 -4
  4. package/README.md +81 -64
  5. package/TECHNICAL_ARCHITECTURE.md +112 -18
  6. package/USER_GUIDE.md +57 -40
  7. package/bin/cli.js +49 -127
  8. package/dist/mcp/gutenberg-converter.js +667 -413
  9. package/dist/mcp/wordpress-server.js +1558 -1181
  10. package/extension.json +3 -3
  11. package/package.json +2 -1
  12. package/skills/content-writer/SKILL.md +130 -178
  13. package/skills/infographic/SKILL.md +191 -0
  14. package/skills/myaidev-analyze/SKILL.md +242 -0
  15. package/skills/myaidev-analyze/agents/dependency-mapper-agent.md +236 -0
  16. package/skills/myaidev-analyze/agents/pattern-detector-agent.md +240 -0
  17. package/skills/myaidev-analyze/agents/structure-scanner-agent.md +171 -0
  18. package/skills/myaidev-analyze/agents/tech-profiler-agent.md +291 -0
  19. package/skills/myaidev-architect/SKILL.md +389 -0
  20. package/skills/myaidev-architect/agents/compliance-checker-agent.md +287 -0
  21. package/skills/myaidev-architect/agents/requirements-analyst-agent.md +194 -0
  22. package/skills/myaidev-architect/agents/system-designer-agent.md +315 -0
  23. package/skills/myaidev-coder/SKILL.md +291 -0
  24. package/skills/myaidev-coder/agents/implementer-agent.md +185 -0
  25. package/skills/myaidev-coder/agents/integration-agent.md +168 -0
  26. package/skills/myaidev-coder/agents/pattern-scanner-agent.md +161 -0
  27. package/skills/myaidev-coder/agents/self-reviewer-agent.md +168 -0
  28. package/skills/myaidev-debug/SKILL.md +308 -0
  29. package/skills/myaidev-debug/agents/fix-agent-debug.md +317 -0
  30. package/skills/myaidev-debug/agents/hypothesis-agent.md +226 -0
  31. package/skills/myaidev-debug/agents/investigator-agent.md +250 -0
  32. package/skills/myaidev-debug/agents/symptom-collector-agent.md +231 -0
  33. package/skills/myaidev-documenter/SKILL.md +194 -0
  34. package/skills/myaidev-documenter/agents/code-reader-agent.md +172 -0
  35. package/skills/myaidev-documenter/agents/doc-validator-agent.md +174 -0
  36. package/skills/myaidev-documenter/agents/doc-writer-agent.md +379 -0
  37. package/skills/myaidev-migrate/SKILL.md +300 -0
  38. package/skills/myaidev-migrate/agents/migration-planner-agent.md +237 -0
  39. package/skills/myaidev-migrate/agents/migration-writer-agent.md +248 -0
  40. package/skills/myaidev-migrate/agents/schema-analyzer-agent.md +190 -0
  41. package/skills/myaidev-performance/SKILL.md +270 -0
  42. package/skills/myaidev-performance/agents/benchmark-agent.md +281 -0
  43. package/skills/myaidev-performance/agents/optimizer-agent.md +277 -0
  44. package/skills/myaidev-performance/agents/profiler-agent.md +252 -0
  45. package/skills/myaidev-refactor/SKILL.md +296 -0
  46. package/skills/myaidev-refactor/agents/refactor-executor-agent.md +221 -0
  47. package/skills/myaidev-refactor/agents/refactor-planner-agent.md +213 -0
  48. package/skills/myaidev-refactor/agents/regression-guard-agent.md +242 -0
  49. package/skills/myaidev-refactor/agents/smell-detector-agent.md +233 -0
  50. package/skills/myaidev-reviewer/SKILL.md +385 -0
  51. package/skills/myaidev-reviewer/agents/auto-fixer-agent.md +238 -0
  52. package/skills/myaidev-reviewer/agents/code-analyst-agent.md +220 -0
  53. package/skills/myaidev-reviewer/agents/security-scanner-agent.md +262 -0
  54. package/skills/myaidev-tester/SKILL.md +331 -0
  55. package/skills/myaidev-tester/agents/coverage-analyst-agent.md +163 -0
  56. package/skills/myaidev-tester/agents/tdd-driver-agent.md +242 -0
  57. package/skills/myaidev-tester/agents/test-runner-agent.md +176 -0
  58. package/skills/myaidev-tester/agents/test-strategist-agent.md +154 -0
  59. package/skills/myaidev-tester/agents/test-writer-agent.md +242 -0
  60. package/skills/myaidev-workflow/SKILL.md +567 -0
  61. package/skills/myaidev-workflow/agents/analyzer-agent.md +317 -0
  62. package/skills/myaidev-workflow/agents/coordinator-agent.md +253 -0
  63. package/skills/security-auditor/SKILL.md +1 -1
  64. package/skills/skill-builder/SKILL.md +417 -0
  65. package/src/cli/commands/addon.js +146 -135
  66. package/src/cli/commands/auth.js +9 -1
  67. package/src/config/workflows.js +11 -6
  68. package/src/lib/ascii-banner.js +3 -3
  69. package/src/lib/update-manager.js +120 -61
  70. package/src/mcp/gutenberg-converter.js +667 -413
  71. package/src/mcp/wordpress-server.js +1558 -1181
  72. package/src/statusline/statusline.sh +279 -0
  73. package/src/templates/claude/CLAUDE.md +124 -0
  74. package/skills/sparc-architect/SKILL.md +0 -127
  75. package/skills/sparc-coder/SKILL.md +0 -90
  76. package/skills/sparc-documenter/SKILL.md +0 -155
  77. package/skills/sparc-reviewer/SKILL.md +0 -138
  78. package/skills/sparc-tester/SKILL.md +0 -100
  79. package/skills/sparc-workflow/SKILL.md +0 -130
  80. /package/{marketplace.json → .claude-plugin/marketplace.json} +0 -0
@@ -4,6 +4,8 @@ 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
6
  import dotenv from "dotenv";
7
+ import fs from "node:fs";
8
+ import path from "node:path";
7
9
  import { GutenbergConverter } from "./gutenberg-converter.js";
8
10
 
9
11
  // Load environment variables
@@ -13,1260 +15,1635 @@ dotenv.config();
13
15
  const WORDPRESS_URL = process.env.WORDPRESS_URL;
14
16
  const WORDPRESS_USERNAME = process.env.WORDPRESS_USERNAME;
15
17
  const WORDPRESS_APP_PASSWORD = process.env.WORDPRESS_APP_PASSWORD;
16
- const WORDPRESS_USE_GUTENBERG = process.env.WORDPRESS_USE_GUTENBERG === 'true';
18
+ const WORDPRESS_USE_GUTENBERG = process.env.WORDPRESS_USE_GUTENBERG === "true";
17
19
 
18
20
  // Validate environment variables
19
21
  if (!WORDPRESS_URL || !WORDPRESS_USERNAME || !WORDPRESS_APP_PASSWORD) {
20
- console.error("Missing required environment variables: WORDPRESS_URL, WORDPRESS_USERNAME, WORDPRESS_APP_PASSWORD");
21
- process.exit(1);
22
+ console.error(
23
+ "Missing required environment variables: WORDPRESS_URL, WORDPRESS_USERNAME, WORDPRESS_APP_PASSWORD",
24
+ );
25
+ process.exit(1);
22
26
  }
23
27
 
24
28
  // Session management
25
29
  class SessionManager {
26
- constructor() {
27
- this.sessions = new Map();
28
- this.activeSession = null;
29
- this.sessionTimeout = 30 * 60 * 1000; // 30 minutes
30
- }
31
-
32
- createSession(id = null) {
33
- const sessionId = id || `wp_session_${Date.now()}`;
34
- const session = {
35
- id: sessionId,
36
- created: Date.now(),
37
- lastActivity: Date.now(),
38
- operations: [],
39
- context: {}
40
- };
41
-
42
- this.sessions.set(sessionId, session);
43
- this.activeSession = sessionId;
44
- return sessionId;
45
- }
46
-
47
- getSession(id) {
48
- return this.sessions.get(id);
49
- }
50
-
51
- updateActivity(id) {
52
- const session = this.sessions.get(id);
53
- if (session) {
54
- session.lastActivity = Date.now();
55
- }
56
- }
57
-
58
- logOperation(sessionId, operation) {
59
- const session = this.sessions.get(sessionId);
60
- if (session) {
61
- session.operations.push({
62
- ...operation,
63
- timestamp: Date.now()
64
- });
65
- }
66
- }
67
-
68
- cleanup() {
69
- const now = Date.now();
70
- for (const [id, session] of this.sessions) {
71
- if (now - session.lastActivity > this.sessionTimeout) {
72
- this.sessions.delete(id);
73
- }
74
- }
75
- }
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
+ }
76
80
  }
77
81
 
78
82
  const sessionManager = new SessionManager();
79
83
 
80
84
  // Memory manager for persistence (simple implementation)
81
85
  class MemoryManager {
82
- constructor() {
83
- this.memory = new Map();
84
- this.namespaces = new Map();
85
- }
86
-
87
- store(key, value, namespace = 'default', ttl = null) {
88
- const nsMap = this.namespaces.get(namespace) || new Map();
89
- const entry = {
90
- value,
91
- timestamp: Date.now(),
92
- ttl: ttl ? Date.now() + ttl : null
93
- };
94
- nsMap.set(key, entry);
95
- this.namespaces.set(namespace, nsMap);
96
- return true;
97
- }
98
-
99
- retrieve(key, namespace = 'default') {
100
- const nsMap = this.namespaces.get(namespace);
101
- if (!nsMap) return null;
102
-
103
- const entry = nsMap.get(key);
104
- if (!entry) return null;
105
-
106
- if (entry.ttl && Date.now() > entry.ttl) {
107
- nsMap.delete(key);
108
- return null;
109
- }
110
-
111
- return entry.value;
112
- }
113
-
114
- delete(key, namespace = 'default') {
115
- const nsMap = this.namespaces.get(namespace);
116
- if (nsMap) {
117
- return nsMap.delete(key);
118
- }
119
- return false;
120
- }
121
-
122
- list(namespace = 'default') {
123
- const nsMap = this.namespaces.get(namespace);
124
- if (!nsMap) return [];
125
-
126
- const now = Date.now();
127
- const results = [];
128
-
129
- for (const [key, entry] of nsMap) {
130
- if (entry.ttl && now > entry.ttl) {
131
- nsMap.delete(key);
132
- continue;
133
- }
134
- results.push({ key, value: entry.value, timestamp: entry.timestamp });
135
- }
136
-
137
- return results;
138
- }
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
+ }
139
143
  }
140
144
 
141
145
  const memoryManager = new MemoryManager();
142
146
 
143
147
  // Create MCP server with enhanced configuration
144
148
  const server = new McpServer({
145
- name: "wordpress-mcp-server",
146
- version: "2.0.0",
147
- 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",
148
153
  });
149
154
 
150
155
  // Enhanced helper function for WordPress API requests with better error handling
151
- async function wordpressRequest(endpoint, method = 'GET', data = null, sessionId = null) {
152
- const baseUrl = WORDPRESS_URL.replace(/\/$/, '');
153
- const apiPath = '/wp-json/wp/v2';
154
- const auth = Buffer.from(`${WORDPRESS_USERNAME}:${WORDPRESS_APP_PASSWORD}`).toString('base64');
155
-
156
- const options = {
157
- method,
158
- headers: {
159
- 'Authorization': `Basic ${auth}`,
160
- 'Content-Type': 'application/json',
161
- 'User-Agent': 'WordPress-MCP-Server/2.0.0'
162
- },
163
- timeout: 30000 // 30 second timeout
164
- };
165
-
166
- if (data && method !== 'GET') {
167
- options.body = JSON.stringify(data);
168
- }
169
-
170
- const startTime = Date.now();
171
- let response;
172
-
173
- try {
174
- response = await fetch(`${baseUrl}${apiPath}${endpoint}`, options);
175
-
176
- // Log operation if session provided
177
- if (sessionId) {
178
- sessionManager.logOperation(sessionId, {
179
- type: 'api_request',
180
- method,
181
- endpoint,
182
- status: response.status,
183
- duration: Date.now() - startTime
184
- });
185
- sessionManager.updateActivity(sessionId);
186
- }
187
-
188
- if (!response.ok) {
189
- let errorBody;
190
- try {
191
- errorBody = await response.json();
192
- } catch {
193
- errorBody = await response.text();
194
- }
195
-
196
- throw new MCPError({
197
- code: response.status,
198
- message: `WordPress API Error: ${response.status} - ${response.statusText}`,
199
- details: errorBody,
200
- endpoint,
201
- method
202
- });
203
- }
204
-
205
- return await response.json();
206
-
207
- } catch (error) {
208
- // Log error operation
209
- if (sessionId) {
210
- sessionManager.logOperation(sessionId, {
211
- type: 'api_error',
212
- method,
213
- endpoint,
214
- error: error.message,
215
- duration: Date.now() - startTime
216
- });
217
- }
218
-
219
- if (error instanceof MCPError) {
220
- throw error;
221
- }
222
-
223
- throw new MCPError({
224
- code: 'NETWORK_ERROR',
225
- message: `Network error: ${error.message}`,
226
- details: error,
227
- endpoint,
228
- method
229
- });
230
- }
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 };
231
387
  }
232
388
 
233
389
  // Custom error class for better error handling
234
390
  class MCPError extends Error {
235
- constructor({ code, message, details, endpoint, method }) {
236
- super(message);
237
- this.name = 'MCPError';
238
- this.code = code;
239
- this.details = details;
240
- this.endpoint = endpoint;
241
- this.method = method;
242
- }
243
-
244
- toJSON() {
245
- return {
246
- success: false,
247
- error: {
248
- code: this.code,
249
- message: this.message,
250
- details: this.details,
251
- endpoint: this.endpoint,
252
- method: this.method,
253
- timestamp: Date.now()
254
- }
255
- };
256
- }
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
+ }
257
413
  }
258
414
 
259
415
  // Enhanced response formatter
260
416
  function formatMCPResponse(data, success = true) {
261
- const response = {
262
- success,
263
- timestamp: Date.now(),
264
- server_version: "2.0.0"
265
- };
266
-
267
- if (success) {
268
- response.data = data;
269
- } else {
270
- response.error = data;
271
- }
272
-
273
- return {
274
- content: [{
275
- type: "text",
276
- text: JSON.stringify(response, null, 2)
277
- }]
278
- };
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
+ };
279
437
  }
280
438
 
281
439
  // Tool execution wrapper with enhanced error handling
282
440
  async function executeTool(toolName, params, handler) {
283
- const sessionId = sessionManager.activeSession || sessionManager.createSession();
284
-
285
- try {
286
- sessionManager.updateActivity(sessionId);
287
-
288
- const startTime = Date.now();
289
- const result = await handler(params, sessionId);
290
- const duration = Date.now() - startTime;
291
-
292
- // Log successful operation
293
- sessionManager.logOperation(sessionId, {
294
- type: 'tool_execution',
295
- tool: toolName,
296
- status: 'success',
297
- duration,
298
- params: Object.keys(params || {})
299
- });
300
-
301
- return result;
302
-
303
- } catch (error) {
304
- // Log failed operation
305
- sessionManager.logOperation(sessionId, {
306
- type: 'tool_execution',
307
- tool: toolName,
308
- status: 'error',
309
- error: error.message,
310
- params: Object.keys(params || {})
311
- });
312
-
313
- if (error instanceof MCPError) {
314
- return formatMCPResponse(error.toJSON().error, false);
315
- }
316
-
317
- return formatMCPResponse({
318
- code: 'TOOL_ERROR',
319
- message: error.message,
320
- tool: toolName,
321
- timestamp: Date.now()
322
- }, false);
323
- }
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
+ }
324
485
  }
325
486
 
326
487
  // Register enhanced WordPress MCP Tools
327
488
 
328
489
  // Session Management Tools
329
- server.registerTool("wp_session_create", {
330
- title: "Create WordPress Session",
331
- description: "Create a new session for tracking operations and maintaining context",
332
- inputSchema: {
333
- type: "object",
334
- properties: {
335
- session_id: {
336
- type: "string",
337
- description: "Optional custom session ID"
338
- },
339
- context: {
340
- type: "object",
341
- description: "Optional context data for the session"
342
- }
343
- },
344
- additionalProperties: false
345
- }
346
- }, async (params) => {
347
- return executeTool('wp_session_create', params, async (params) => {
348
- const sessionId = sessionManager.createSession(params.session_id);
349
- if (params.context) {
350
- const session = sessionManager.getSession(sessionId);
351
- session.context = { ...session.context, ...params.context };
352
- }
353
-
354
- return formatMCPResponse({
355
- session_id: sessionId,
356
- created: true,
357
- message: "Session created successfully"
358
- });
359
- });
360
- });
361
-
362
- server.registerTool("wp_session_status", {
363
- title: "Get WordPress Session Status",
364
- description: "Get current session information and operation history",
365
- inputSchema: {
366
- type: "object",
367
- properties: {
368
- session_id: {
369
- type: "string",
370
- description: "Session ID to check status for"
371
- }
372
- },
373
- additionalProperties: false
374
- }
375
- }, async (params) => {
376
- return executeTool('wp_session_status', params, async (params) => {
377
- const sessionId = params.session_id || sessionManager.activeSession;
378
- if (!sessionId) {
379
- throw new MCPError({
380
- code: 'NO_SESSION',
381
- message: 'No active session found'
382
- });
383
- }
384
-
385
- const session = sessionManager.getSession(sessionId);
386
- if (!session) {
387
- throw new MCPError({
388
- code: 'SESSION_NOT_FOUND',
389
- message: `Session ${sessionId} not found`
390
- });
391
- }
392
-
393
- return formatMCPResponse({
394
- session: {
395
- id: session.id,
396
- created: new Date(session.created).toISOString(),
397
- last_activity: new Date(session.lastActivity).toISOString(),
398
- operations_count: session.operations.length,
399
- recent_operations: session.operations.slice(-5),
400
- context: session.context
401
- }
402
- });
403
- });
404
- });
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
+ );
405
565
 
406
566
  // Memory Management Tools
407
- server.registerTool("wp_memory_store", {
408
- title: "Store WordPress Memory",
409
- description: "Store data in memory for persistence across operations",
410
- inputSchema: {
411
- type: "object",
412
- properties: {
413
- key: {
414
- type: "string",
415
- description: "Memory key to store data under"
416
- },
417
- value: {
418
- description: "Value to store (any type)"
419
- },
420
- namespace: {
421
- type: "string",
422
- description: "Namespace to organize data",
423
- default: "wordpress"
424
- },
425
- ttl: {
426
- type: "number",
427
- description: "Time-to-live in milliseconds"
428
- }
429
- },
430
- required: ["key", "value"],
431
- additionalProperties: false
432
- }
433
- }, async (params) => {
434
- return executeTool('wp_memory_store', params, async (params) => {
435
- const stored = memoryManager.store(
436
- params.key,
437
- params.value,
438
- params.namespace,
439
- params.ttl
440
- );
441
-
442
- return formatMCPResponse({
443
- stored: stored,
444
- key: params.key,
445
- namespace: params.namespace,
446
- ttl: params.ttl,
447
- message: "Data stored successfully"
448
- });
449
- });
450
- });
451
-
452
- server.registerTool("wp_memory_retrieve", {
453
- title: "Retrieve WordPress Memory",
454
- description: "Retrieve stored data from memory",
455
- inputSchema: {
456
- type: "object",
457
- properties: {
458
- key: {
459
- type: "string",
460
- description: "Memory key to retrieve"
461
- },
462
- namespace: {
463
- type: "string",
464
- description: "Namespace to search in",
465
- default: "wordpress"
466
- }
467
- },
468
- required: ["key"],
469
- additionalProperties: false
470
- }
471
- }, async (params) => {
472
- return executeTool('wp_memory_retrieve', params, async (params) => {
473
- const value = memoryManager.retrieve(params.key, params.namespace);
474
-
475
- return formatMCPResponse({
476
- key: params.key,
477
- namespace: params.namespace,
478
- value: value,
479
- found: value !== null
480
- });
481
- });
482
- });
483
-
484
- server.registerTool("wp_memory_list", {
485
- title: "List WordPress Memory",
486
- description: "List all stored data in a namespace",
487
- inputSchema: {
488
- type: "object",
489
- properties: {
490
- namespace: {
491
- type: "string",
492
- description: "Namespace to list data from",
493
- default: "wordpress"
494
- }
495
- },
496
- additionalProperties: false
497
- }
498
- }, async (params) => {
499
- return executeTool('wp_memory_list', params, async (params) => {
500
- const entries = memoryManager.list(params.namespace);
501
-
502
- return formatMCPResponse({
503
- namespace: params.namespace,
504
- entries: entries,
505
- count: entries.length
506
- });
507
- });
508
- });
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
+ );
509
656
 
510
657
  // Health and System Tools
511
- server.registerTool("wp_health_check", {
512
- title: "WordPress Health Check",
513
- description: "Comprehensive health check of WordPress site and MCP server",
514
- inputSchema: {
515
- type: "object",
516
- properties: {
517
- detailed: {
518
- type: "boolean",
519
- description: "Include detailed health metrics",
520
- default: false
521
- }
522
- },
523
- additionalProperties: false
524
- }
525
- }, async (params) => {
526
- return executeTool('wp_health_check', params, async (params, sessionId) => {
527
- const checks = [];
528
-
529
- try {
530
- // Test WordPress API connectivity
531
- const startTime = Date.now();
532
- const response = await wordpressRequest('/', 'GET', null, sessionId);
533
- checks.push({
534
- name: 'WordPress API Connectivity',
535
- status: 'passed',
536
- response_time: Date.now() - startTime,
537
- message: 'API is responding'
538
- });
539
- } catch (error) {
540
- checks.push({
541
- name: 'WordPress API Connectivity',
542
- status: 'failed',
543
- error: error.message
544
- });
545
- }
546
-
547
- // Check authentication
548
- try {
549
- await wordpressRequest('/users/me', 'GET', null, sessionId);
550
- checks.push({
551
- name: 'Authentication',
552
- status: 'passed',
553
- message: 'Authentication working'
554
- });
555
- } catch (error) {
556
- checks.push({
557
- name: 'Authentication',
558
- status: 'failed',
559
- error: error.message
560
- });
561
- }
562
-
563
- // Session status
564
- const activeSessionCount = sessionManager.sessions.size;
565
- checks.push({
566
- name: 'Session Management',
567
- status: 'passed',
568
- active_sessions: activeSessionCount,
569
- current_session: sessionId
570
- });
571
-
572
- // Memory status
573
- const memoryEntries = memoryManager.list('wordpress');
574
- checks.push({
575
- name: 'Memory Management',
576
- status: 'passed',
577
- stored_entries: memoryEntries.length
578
- });
579
-
580
- const overall = checks.every(check => check.status === 'passed');
581
-
582
- return formatMCPResponse({
583
- overall_status: overall ? 'healthy' : 'unhealthy',
584
- checks: checks,
585
- timestamp: Date.now(),
586
- server_version: '2.0.0'
587
- });
588
- });
589
- });
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
+ );
590
737
 
591
738
  // Get site information
592
- server.registerTool("wp_get_site_info", {
593
- title: "Get WordPress Site Information",
594
- description: "Get WordPress site statistics, version, and health information",
595
- inputSchema: {
596
- type: "object",
597
- properties: {
598
- include_health: {
599
- type: "boolean",
600
- description: "Include health metrics in response",
601
- default: false
602
- },
603
- cache_duration: {
604
- type: "number",
605
- description: "Cache duration in seconds",
606
- default: 300
607
- }
608
- },
609
- additionalProperties: false
610
- }
611
- }, async (params) => {
612
- return executeTool('wp_get_site_info', params, async (params, sessionId) => {
613
- // Check cache first
614
- const cacheKey = `site_info_${WORDPRESS_URL}`;
615
- const cached = memoryManager.retrieve(cacheKey, 'cache');
616
- if (cached && Date.now() - cached.timestamp < params.cache_duration * 1000) {
617
- return formatMCPResponse({
618
- ...cached.data,
619
- cached: true,
620
- cache_age: Math.floor((Date.now() - cached.timestamp) / 1000)
621
- });
622
- }
623
-
624
- // Get site info from multiple endpoints
625
- const [users, posts, plugins] = await Promise.all([
626
- wordpressRequest('/users', 'GET', null, sessionId),
627
- wordpressRequest('/posts?per_page=1', 'GET', null, sessionId),
628
- wordpressRequest('/plugins', 'GET', null, sessionId).catch(() => []) // Plugins endpoint might not be available
629
- ]);
630
-
631
- const siteInfo = {
632
- site_url: WORDPRESS_URL,
633
- total_users: users.length,
634
- total_posts: posts.length,
635
- total_plugins: plugins.length,
636
- wordpress_version: "Available via REST API",
637
- server_version: "2.0.0",
638
- last_updated: new Date().toISOString(),
639
- message: "Site information retrieved successfully"
640
- };
641
-
642
- // Store in cache
643
- memoryManager.store(cacheKey, {
644
- data: siteInfo,
645
- timestamp: Date.now()
646
- }, 'cache', params.cache_duration * 1000);
647
-
648
- if (params.include_health) {
649
- // Include basic health metrics
650
- siteInfo.health_metrics = {
651
- api_responsive: true,
652
- authenticated: true,
653
- last_check: Date.now()
654
- };
655
- }
656
-
657
- return formatMCPResponse(siteInfo);
658
- });
659
- });
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
+ );
660
820
 
661
821
  // List posts
662
- server.registerTool("wp_list_posts", {
663
- title: "List WordPress Posts",
664
- description: "Get posts with filtering options",
665
- inputSchema: z.object({
666
- per_page: z.number().optional().default(10),
667
- page: z.number().optional().default(1),
668
- status: z.string().optional().default("publish"),
669
- search: z.string().optional(),
670
- categories: z.array(z.number()).optional(),
671
- tags: z.array(z.number()).optional()
672
- })
673
- }, async (params) => {
674
- try {
675
- const queryParams = new URLSearchParams();
676
-
677
- queryParams.append('per_page', params.per_page.toString());
678
- queryParams.append('page', params.page.toString());
679
- queryParams.append('status', params.status);
680
-
681
- if (params.search) queryParams.append('search', params.search);
682
- if (params.categories) queryParams.append('categories', params.categories.join(','));
683
- if (params.tags) queryParams.append('tags', params.tags.join(','));
684
-
685
- const posts = await wordpressRequest(`/posts?${queryParams.toString()}`);
686
-
687
- return {
688
- content: [{
689
- type: "text",
690
- text: JSON.stringify({
691
- success: true,
692
- posts: posts.map(post => ({
693
- id: post.id,
694
- title: post.title.rendered,
695
- status: post.status,
696
- date: post.date,
697
- link: post.link,
698
- excerpt: post.excerpt.rendered.replace(/<[^>]*>/g, '').trim()
699
- })),
700
- total: posts.length
701
- }, null, 2)
702
- }]
703
- };
704
- } catch (error) {
705
- return {
706
- content: [{
707
- type: "text",
708
- text: JSON.stringify({
709
- success: false,
710
- error: error.message
711
- }, null, 2)
712
- }]
713
- };
714
- }
715
- });
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
+ );
716
893
 
717
894
  // Create post
718
- server.registerTool("wp_create_post", {
719
- title: "Create WordPress Post",
720
- description: "Create a new WordPress post or page with enhanced tracking and validation",
721
- inputSchema: {
722
- type: "object",
723
- properties: {
724
- title: {
725
- type: "string",
726
- description: "Post title (required)",
727
- minLength: 1
728
- },
729
- content: {
730
- type: "string",
731
- description: "Post content (required)",
732
- minLength: 1
733
- },
734
- status: {
735
- type: "string",
736
- enum: ["draft", "publish", "private", "pending"],
737
- description: "Post status",
738
- default: "draft"
739
- },
740
- excerpt: {
741
- type: "string",
742
- description: "Post excerpt"
743
- },
744
- slug: {
745
- type: "string",
746
- description: "Post slug/URL"
747
- },
748
- categories: {
749
- type: "array",
750
- items: { type: "number" },
751
- description: "Category IDs",
752
- default: []
753
- },
754
- tags: {
755
- type: "array",
756
- items: { type: "number" },
757
- description: "Tag IDs",
758
- default: []
759
- },
760
- use_gutenberg: {
761
- type: "boolean",
762
- description: "Use Gutenberg block format",
763
- default: false
764
- },
765
- store_in_memory: {
766
- type: "boolean",
767
- description: "Store post data in memory for tracking",
768
- default: true
769
- }
770
- },
771
- required: ["title", "content"],
772
- additionalProperties: false
773
- }
774
- }, async (params) => {
775
- return executeTool('wp_create_post', params, async (params, sessionId) => {
776
- // Convert content to Gutenberg format if requested
777
- let content = params.content;
778
- const useGutenberg = params.use_gutenberg || params.gutenberg || WORDPRESS_USE_GUTENBERG;
779
-
780
- if (useGutenberg) {
781
- try {
782
- content = GutenbergConverter.htmlToGutenberg(params.content);
783
- } catch (gutenbergError) {
784
- // If Gutenberg conversion fails, log warning but continue with original content
785
- sessionManager.logOperation(sessionId, {
786
- type: 'warning',
787
- message: 'Gutenberg conversion failed, using original content',
788
- error: gutenbergError.message
789
- });
790
- }
791
- }
792
-
793
- const postData = {
794
- title: params.title,
795
- content: content,
796
- status: params.status,
797
- categories: params.categories,
798
- tags: params.tags
799
- };
800
-
801
- // Add optional fields
802
- if (params.excerpt) postData.excerpt = params.excerpt;
803
- if (params.slug) postData.slug = params.slug;
804
- if (params.meta) postData.meta = params.meta;
805
- if (params.featured_media) postData.featured_media = params.featured_media;
806
- if (params.author) postData.author = params.author;
807
- if (params.comment_status) postData.comment_status = params.comment_status;
808
- if (params.ping_status) postData.ping_status = params.ping_status;
809
-
810
- const post = await wordpressRequest('/posts', 'POST', postData, sessionId);
811
-
812
- const result = {
813
- post_id: post.id,
814
- post_url: post.link,
815
- edit_url: `${WORDPRESS_URL.replace(/\/$/, '')}/wp-admin/post.php?post=${post.id}&action=edit`,
816
- title: post.title.rendered,
817
- status: post.status,
818
- date: post.date,
819
- modified: post.modified,
820
- gutenberg_used: useGutenberg,
821
- categories: post.categories,
822
- tags: post.tags,
823
- message: `Post created successfully with ${useGutenberg ? 'Gutenberg' : 'Classic'} editor format`
824
- };
825
-
826
- // Store post reference in memory if requested
827
- if (params.store_in_memory) {
828
- memoryManager.store(`created_post_${post.id}`, {
829
- ...result,
830
- original_params: params,
831
- created_at: Date.now()
832
- }, 'posts', 3600000); // 1 hour TTL
833
- }
834
-
835
- return formatMCPResponse(result);
836
- });
837
- });
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
+ );
838
1026
 
839
1027
  // Update post
840
- server.registerTool("wp_update_post", {
841
- title: "Update WordPress Post",
842
- description: "Update an existing WordPress post",
843
- inputSchema: z.object({
844
- id: z.number(),
845
- title: z.string().optional(),
846
- content: z.string().optional(),
847
- status: z.string().optional(),
848
- excerpt: z.string().optional(),
849
- slug: z.string().optional(),
850
- categories: z.array(z.number()).optional(),
851
- tags: z.array(z.number()).optional()
852
- })
853
- }, async (params) => {
854
- try {
855
- const { id, ...updateData } = params;
856
- const filteredData = Object.fromEntries(
857
- Object.entries(updateData).filter(([_, value]) => value !== undefined)
858
- );
859
-
860
- const post = await wordpressRequest(`/posts/${id}`, 'POST', filteredData);
861
-
862
- return {
863
- content: [{
864
- type: "text",
865
- text: JSON.stringify({
866
- success: true,
867
- post_id: post.id,
868
- post_url: post.link,
869
- message: "Post updated successfully"
870
- }, null, 2)
871
- }]
872
- };
873
- } catch (error) {
874
- return {
875
- content: [{
876
- type: "text",
877
- text: JSON.stringify({
878
- success: false,
879
- error: error.message
880
- }, null, 2)
881
- }]
882
- };
883
- }
884
- });
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
+ );
885
1089
 
886
1090
  // Delete post
887
- server.registerTool("wp_delete_post", {
888
- title: "Delete WordPress Post",
889
- description: "Delete a WordPress post (move to trash)",
890
- inputSchema: z.object({
891
- id: z.number()
892
- })
893
- }, async (params) => {
894
- try {
895
- const post = await wordpressRequest(`/posts/${params.id}`, 'DELETE');
896
-
897
- return {
898
- content: [{
899
- type: "text",
900
- text: JSON.stringify({
901
- success: true,
902
- post_id: post.id,
903
- message: "Post deleted successfully"
904
- }, null, 2)
905
- }]
906
- };
907
- } catch (error) {
908
- return {
909
- content: [{
910
- type: "text",
911
- text: JSON.stringify({
912
- success: false,
913
- error: error.message
914
- }, null, 2)
915
- }]
916
- };
917
- }
918
- });
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
+ );
919
1139
 
920
1140
  // List users
921
- server.registerTool("wp_list_users", {
922
- title: "List WordPress Users",
923
- description: "Get WordPress user accounts and roles",
924
- inputSchema: z.object({
925
- per_page: z.number().optional().default(100),
926
- role: z.string().optional()
927
- })
928
- }, async (params) => {
929
- try {
930
- const queryParams = new URLSearchParams();
931
- queryParams.append('per_page', params.per_page.toString());
932
- if (params.role) queryParams.append('role', params.role);
933
-
934
- const users = await wordpressRequest(`/users?${queryParams.toString()}`);
935
-
936
- return {
937
- content: [{
938
- type: "text",
939
- text: JSON.stringify({
940
- success: true,
941
- users: users.map(user => ({
942
- id: user.id,
943
- username: user.username,
944
- name: user.name,
945
- email: user.email,
946
- roles: user.roles,
947
- registered_date: user.registered_date
948
- })),
949
- total: users.length
950
- }, null, 2)
951
- }]
952
- };
953
- } catch (error) {
954
- return {
955
- content: [{
956
- type: "text",
957
- text: JSON.stringify({
958
- success: false,
959
- error: error.message
960
- }, null, 2)
961
- }]
962
- };
963
- }
964
- });
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
+ );
965
1201
 
966
1202
  // Create user
967
- server.registerTool("wp_create_user", {
968
- title: "Create WordPress User",
969
- description: "Add a new WordPress user account",
970
- inputSchema: z.object({
971
- username: z.string(),
972
- email: z.string().email(),
973
- password: z.string(),
974
- roles: z.array(z.string()).optional().default(["subscriber"])
975
- })
976
- }, async (params) => {
977
- try {
978
- const userData = {
979
- username: params.username,
980
- email: params.email,
981
- password: params.password,
982
- roles: params.roles
983
- };
984
-
985
- const user = await wordpressRequest('/users', 'POST', userData);
986
-
987
- return {
988
- content: [{
989
- type: "text",
990
- text: JSON.stringify({
991
- success: true,
992
- user_id: user.id,
993
- username: user.username,
994
- email: user.email,
995
- roles: user.roles,
996
- message: "User created successfully"
997
- }, null, 2)
998
- }]
999
- };
1000
- } catch (error) {
1001
- return {
1002
- content: [{
1003
- type: "text",
1004
- text: JSON.stringify({
1005
- success: false,
1006
- error: error.message
1007
- }, null, 2)
1008
- }]
1009
- };
1010
- }
1011
- });
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
+ );
1012
1264
 
1013
1265
  // Update user
1014
- server.registerTool("wp_update_user", {
1015
- title: "Update WordPress User",
1016
- description: "Modify WordPress user settings",
1017
- inputSchema: z.object({
1018
- id: z.number(),
1019
- email: z.string().email().optional(),
1020
- roles: z.array(z.string()).optional(),
1021
- password: z.string().optional()
1022
- })
1023
- }, async (params) => {
1024
- try {
1025
- const { id, ...updateData } = params;
1026
- const filteredData = Object.fromEntries(
1027
- Object.entries(updateData).filter(([_, value]) => value !== undefined)
1028
- );
1029
-
1030
- const user = await wordpressRequest(`/users/${id}`, 'POST', filteredData);
1031
-
1032
- return {
1033
- content: [{
1034
- type: "text",
1035
- text: JSON.stringify({
1036
- success: true,
1037
- user_id: user.id,
1038
- username: user.username,
1039
- email: user.email,
1040
- roles: user.roles,
1041
- message: "User updated successfully"
1042
- }, null, 2)
1043
- }]
1044
- };
1045
- } catch (error) {
1046
- return {
1047
- content: [{
1048
- type: "text",
1049
- text: JSON.stringify({
1050
- success: false,
1051
- error: error.message
1052
- }, null, 2)
1053
- }]
1054
- };
1055
- }
1056
- });
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
+ );
1057
1325
 
1058
1326
  // List plugins (if available)
1059
- server.registerTool("wp_list_plugins", {
1060
- title: "List WordPress Plugins",
1061
- description: "Get installed WordPress plugins",
1062
- inputSchema: z.object({})
1063
- }, async () => {
1064
- try {
1065
- const plugins = await wordpressRequest('/plugins');
1066
-
1067
- return {
1068
- content: [{
1069
- type: "text",
1070
- text: JSON.stringify({
1071
- success: true,
1072
- plugins: plugins.map(plugin => ({
1073
- name: plugin.name,
1074
- plugin: plugin.plugin,
1075
- status: plugin.status,
1076
- version: plugin.version
1077
- })),
1078
- total: plugins.length
1079
- }, null, 2)
1080
- }]
1081
- };
1082
- } catch (error) {
1083
- return {
1084
- content: [{
1085
- type: "text",
1086
- text: JSON.stringify({
1087
- success: false,
1088
- error: error.message,
1089
- note: "Plugins endpoint may not be available on all WordPress installations"
1090
- }, null, 2)
1091
- }]
1092
- };
1093
- }
1094
- });
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
+ );
1095
1379
 
1096
1380
  // Get media
1097
- server.registerTool("wp_get_media", {
1098
- title: "Get WordPress Media",
1099
- description: "Access WordPress media library",
1100
- inputSchema: z.object({
1101
- per_page: z.number().optional().default(10),
1102
- media_type: z.string().optional()
1103
- })
1104
- }, async (params) => {
1105
- try {
1106
- const queryParams = new URLSearchParams();
1107
- queryParams.append('per_page', params.per_page.toString());
1108
- if (params.media_type) queryParams.append('media_type', params.media_type);
1109
-
1110
- const media = await wordpressRequest(`/media?${queryParams.toString()}`);
1111
-
1112
- return {
1113
- content: [{
1114
- type: "text",
1115
- text: JSON.stringify({
1116
- success: true,
1117
- media: media.map(item => ({
1118
- id: item.id,
1119
- title: item.title.rendered,
1120
- source_url: item.source_url,
1121
- media_type: item.media_type,
1122
- mime_type: item.mime_type,
1123
- date: item.date
1124
- })),
1125
- total: media.length
1126
- }, null, 2)
1127
- }]
1128
- };
1129
- } catch (error) {
1130
- return {
1131
- content: [{
1132
- type: "text",
1133
- text: JSON.stringify({
1134
- success: false,
1135
- error: error.message
1136
- }, null, 2)
1137
- }]
1138
- };
1139
- }
1140
- });
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
+ );
1141
1442
 
1142
1443
  // Batch operations tool
1143
- server.registerTool("wp_batch_publish", {
1144
- title: "WordPress Batch Publish",
1145
- description: "Publish multiple posts from markdown files with enhanced tracking",
1146
- inputSchema: z.object({
1147
- files: z.array(z.object({
1148
- path: z.string(),
1149
- title: z.string().optional(),
1150
- status: z.enum(["draft", "publish", "private", "pending"]).optional().default("draft"),
1151
- categories: z.array(z.number()).optional().default([]),
1152
- tags: z.array(z.number()).optional().default([])
1153
- })),
1154
- use_gutenberg: z.boolean().optional().default(WORDPRESS_USE_GUTENBERG),
1155
- dry_run: z.boolean().optional().default(false)
1156
- })
1157
- }, async (params) => {
1158
- return executeTool('wp_batch_publish', params, async (params, sessionId) => {
1159
- const results = [];
1160
- const errors = [];
1161
-
1162
- for (const file of params.files) {
1163
- try {
1164
- if (params.dry_run) {
1165
- results.push({
1166
- file: file.path,
1167
- status: 'dry_run',
1168
- message: 'Would process this file'
1169
- });
1170
- continue;
1171
- }
1172
-
1173
- const postData = {
1174
- title: file.title || `Post from ${file.path}`,
1175
- content: `Content from ${file.path}`,
1176
- status: file.status,
1177
- categories: file.categories,
1178
- tags: file.tags,
1179
- use_gutenberg: params.use_gutenberg,
1180
- store_in_memory: true
1181
- };
1182
-
1183
- const post = await wordpressRequest('/posts', 'POST', postData, sessionId);
1184
-
1185
- results.push({
1186
- file: file.path,
1187
- post_id: post.id,
1188
- post_url: post.link,
1189
- status: 'success',
1190
- message: 'Published successfully'
1191
- });
1192
-
1193
- } catch (error) {
1194
- errors.push({
1195
- file: file.path,
1196
- error: error.message,
1197
- status: 'error'
1198
- });
1199
- }
1200
- }
1201
-
1202
- // Store batch operation result in memory
1203
- const batchId = `batch_${Date.now()}`;
1204
- memoryManager.store(batchId, {
1205
- operation: 'batch_publish',
1206
- results,
1207
- errors,
1208
- total_files: params.files.length,
1209
- successful: results.filter(r => r.status === 'success').length,
1210
- failed: errors.length,
1211
- completed_at: Date.now()
1212
- }, 'batch_operations', 3600000); // 1 hour TTL
1213
-
1214
- return formatMCPResponse({
1215
- batch_id: batchId,
1216
- summary: {
1217
- total_files: params.files.length,
1218
- successful: results.filter(r => r.status === 'success').length,
1219
- failed: errors.length,
1220
- dry_run: params.dry_run
1221
- },
1222
- results,
1223
- errors
1224
- });
1225
- });
1226
- });
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
+ );
1227
1600
 
1228
1601
  // Resource cleanup and server lifecycle management
1229
1602
  function setupCleanupHandlers() {
1230
- // Cleanup sessions every 10 minutes
1231
- setInterval(() => {
1232
- sessionManager.cleanup();
1233
- }, 10 * 60 * 1000);
1234
-
1235
- // Handle graceful shutdown
1236
- const cleanup = () => {
1237
- console.error("WordPress MCP Server shutting down...");
1238
- process.exit(0);
1239
- };
1240
-
1241
- process.on('SIGINT', cleanup);
1242
- process.on('SIGTERM', cleanup);
1243
- 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);
1244
1620
  }
1245
1621
 
1246
1622
  // Start the enhanced MCP server
1247
1623
  async function main() {
1248
- try {
1249
- // Initialize session manager
1250
- sessionManager.createSession('default_session');
1251
-
1252
- // Setup cleanup handlers
1253
- setupCleanupHandlers();
1254
-
1255
- const transport = new StdioServerTransport();
1256
- await server.connect(transport);
1257
-
1258
- console.error("Enhanced WordPress MCP Server v2.0.0 running...");
1259
- console.error(`Connected to WordPress site: ${WORDPRESS_URL}`);
1260
- console.error(`Active session: ${sessionManager.activeSession}`);
1261
- console.error("Available tools: session management, memory management, health checks, WordPress operations, batch publishing");
1262
-
1263
- } catch (error) {
1264
- console.error("Failed to start server:", error.message);
1265
- process.exit(1);
1266
- }
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
+ }
1267
1644
  }
1268
1645
 
1269
1646
  main().catch((error) => {
1270
- console.error("Server startup error:", error);
1271
- process.exit(1);
1647
+ console.error("Server startup error:", error);
1648
+ process.exit(1);
1272
1649
  });