mcp-wordpress 3.1.12 → 3.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/api.d.ts.map +1 -1
- package/dist/client/api.js +4 -3
- package/dist/client/api.js.map +1 -1
- package/dist/tools/cache.d.ts +35 -50
- package/dist/tools/cache.d.ts.map +1 -1
- package/dist/tools/cache.js +29 -65
- package/dist/tools/cache.js.map +1 -1
- package/dist/tools/media.d.ts.map +1 -1
- package/dist/tools/media.js +8 -2
- package/dist/tools/media.js.map +1 -1
- package/dist/tools/pages.d.ts.map +1 -1
- package/dist/tools/pages.js +10 -2
- package/dist/tools/pages.js.map +1 -1
- package/dist/tools/performance/PerformanceTools.d.ts.map +1 -1
- package/dist/tools/performance/PerformanceTools.js +6 -2
- package/dist/tools/performance/PerformanceTools.js.map +1 -1
- package/dist/tools/seo/auditors/SiteAuditor.d.ts.map +1 -1
- package/dist/tools/seo/auditors/SiteAuditor.js +17 -3
- package/dist/tools/seo/auditors/SiteAuditor.js.map +1 -1
- package/dist/tools/site.d.ts.map +1 -1
- package/dist/tools/site.js +2 -2
- package/dist/tools/site.js.map +1 -1
- package/dist/tools/users.d.ts.map +1 -1
- package/dist/tools/users.js +5 -3
- package/dist/tools/users.js.map +1 -1
- package/package.json +4 -3
- package/src/client/api.ts +4 -3
- package/src/tools/cache.ts +29 -75
- package/src/tools/media.ts +9 -2
- package/src/tools/pages.ts +15 -2
- package/src/tools/performance/PerformanceTools.ts +8 -2
- package/src/tools/seo/auditors/SiteAuditor.ts +16 -3
- package/src/tools/site.ts +4 -2
- package/src/tools/users.ts +5 -3
package/src/tools/cache.ts
CHANGED
|
@@ -21,55 +21,42 @@ export class CacheTools {
|
|
|
21
21
|
{
|
|
22
22
|
name: "wp_cache_stats",
|
|
23
23
|
description: "Get cache statistics for a WordPress site.",
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
description:
|
|
29
|
-
"Site ID to get cache stats for. If not provided, uses default site or fails if multiple sites configured.",
|
|
30
|
-
},
|
|
31
|
-
],
|
|
24
|
+
inputSchema: {
|
|
25
|
+
type: "object" as const,
|
|
26
|
+
properties: {},
|
|
27
|
+
},
|
|
32
28
|
handler: this.handleGetCacheStats.bind(this),
|
|
33
29
|
},
|
|
34
30
|
{
|
|
35
31
|
name: "wp_cache_clear",
|
|
36
32
|
description: "Clear cache for a WordPress site.",
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: "object" as const,
|
|
35
|
+
properties: {
|
|
36
|
+
pattern: {
|
|
37
|
+
type: "string",
|
|
38
|
+
description: 'Optional pattern to clear specific cache entries (e.g., "posts", "categories").',
|
|
39
|
+
},
|
|
42
40
|
},
|
|
43
|
-
|
|
44
|
-
name: "pattern",
|
|
45
|
-
type: "string",
|
|
46
|
-
description: 'Optional pattern to clear specific cache entries (e.g., "posts", "categories").',
|
|
47
|
-
},
|
|
48
|
-
],
|
|
41
|
+
},
|
|
49
42
|
handler: this.handleClearCache.bind(this),
|
|
50
43
|
},
|
|
51
44
|
{
|
|
52
45
|
name: "wp_cache_warm",
|
|
53
46
|
description: "Pre-warm cache with essential WordPress data.",
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
description: "Site ID to warm cache for.",
|
|
59
|
-
},
|
|
60
|
-
],
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: "object" as const,
|
|
49
|
+
properties: {},
|
|
50
|
+
},
|
|
61
51
|
handler: this.handleWarmCache.bind(this),
|
|
62
52
|
},
|
|
63
53
|
{
|
|
64
54
|
name: "wp_cache_info",
|
|
65
55
|
description: "Get detailed cache configuration and status information.",
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
description: "Site ID to get cache info for.",
|
|
71
|
-
},
|
|
72
|
-
],
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: "object" as const,
|
|
58
|
+
properties: {},
|
|
59
|
+
},
|
|
73
60
|
handler: this.handleGetCacheInfo.bind(this),
|
|
74
61
|
},
|
|
75
62
|
];
|
|
@@ -78,10 +65,8 @@ export class CacheTools {
|
|
|
78
65
|
/**
|
|
79
66
|
* Get cache statistics
|
|
80
67
|
*/
|
|
81
|
-
async handleGetCacheStats(
|
|
68
|
+
async handleGetCacheStats(client: WordPressClient, _params: Record<string, unknown>) {
|
|
82
69
|
return toolWrapper(async () => {
|
|
83
|
-
const client = this.resolveClient(params.site);
|
|
84
|
-
|
|
85
70
|
if (!(client instanceof CachedWordPressClient)) {
|
|
86
71
|
return {
|
|
87
72
|
caching_enabled: false,
|
|
@@ -112,10 +97,8 @@ export class CacheTools {
|
|
|
112
97
|
/**
|
|
113
98
|
* Clear cache
|
|
114
99
|
*/
|
|
115
|
-
async handleClearCache(
|
|
100
|
+
async handleClearCache(client: WordPressClient, params: Record<string, unknown>) {
|
|
116
101
|
return toolWrapper(async () => {
|
|
117
|
-
const client = this.resolveClient(params.site);
|
|
118
|
-
|
|
119
102
|
if (!(client instanceof CachedWordPressClient)) {
|
|
120
103
|
return {
|
|
121
104
|
success: false,
|
|
@@ -124,14 +107,15 @@ export class CacheTools {
|
|
|
124
107
|
}
|
|
125
108
|
|
|
126
109
|
let cleared: number;
|
|
110
|
+
const pattern = params.pattern as string | undefined;
|
|
127
111
|
|
|
128
|
-
if (
|
|
129
|
-
cleared = client.clearCachePattern(
|
|
112
|
+
if (pattern) {
|
|
113
|
+
cleared = client.clearCachePattern(pattern);
|
|
130
114
|
return {
|
|
131
115
|
success: true,
|
|
132
|
-
message: `Cleared ${cleared} cache entries matching pattern "${
|
|
116
|
+
message: `Cleared ${cleared} cache entries matching pattern "${pattern}".`,
|
|
133
117
|
cleared_entries: cleared,
|
|
134
|
-
pattern
|
|
118
|
+
pattern,
|
|
135
119
|
};
|
|
136
120
|
} else {
|
|
137
121
|
cleared = client.clearCache();
|
|
@@ -147,10 +131,8 @@ export class CacheTools {
|
|
|
147
131
|
/**
|
|
148
132
|
* Warm cache with essential data
|
|
149
133
|
*/
|
|
150
|
-
async handleWarmCache(
|
|
134
|
+
async handleWarmCache(client: WordPressClient, _params: Record<string, unknown>) {
|
|
151
135
|
return toolWrapper(async () => {
|
|
152
|
-
const client = this.resolveClient(params.site);
|
|
153
|
-
|
|
154
136
|
if (!(client instanceof CachedWordPressClient)) {
|
|
155
137
|
return {
|
|
156
138
|
success: false,
|
|
@@ -174,10 +156,8 @@ export class CacheTools {
|
|
|
174
156
|
/**
|
|
175
157
|
* Get detailed cache information
|
|
176
158
|
*/
|
|
177
|
-
async handleGetCacheInfo(
|
|
159
|
+
async handleGetCacheInfo(client: WordPressClient, _params: Record<string, unknown>) {
|
|
178
160
|
return toolWrapper(async () => {
|
|
179
|
-
const client = this.resolveClient(params.site);
|
|
180
|
-
|
|
181
161
|
if (!(client instanceof CachedWordPressClient)) {
|
|
182
162
|
return {
|
|
183
163
|
caching_enabled: false,
|
|
@@ -224,32 +204,6 @@ export class CacheTools {
|
|
|
224
204
|
};
|
|
225
205
|
});
|
|
226
206
|
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Resolve client from site parameter
|
|
230
|
-
*/
|
|
231
|
-
private resolveClient(siteId?: string): WordPressClient {
|
|
232
|
-
if (!siteId) {
|
|
233
|
-
if (this.clients.size === 1) {
|
|
234
|
-
return Array.from(this.clients.values())[0];
|
|
235
|
-
} else if (this.clients.size === 0) {
|
|
236
|
-
throw new Error("No WordPress sites configured.");
|
|
237
|
-
} else {
|
|
238
|
-
throw new Error(
|
|
239
|
-
`Multiple sites configured. Please specify --site parameter. Available sites: ${Array.from(
|
|
240
|
-
this.clients.keys(),
|
|
241
|
-
).join(", ")}`,
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const client = this.clients.get(siteId);
|
|
247
|
-
if (!client) {
|
|
248
|
-
throw new Error(`Site "${siteId}" not found. Available sites: ${Array.from(this.clients.keys()).join(", ")}`);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return client;
|
|
252
|
-
}
|
|
253
207
|
}
|
|
254
208
|
|
|
255
209
|
export default CacheTools;
|
package/src/tools/media.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { WordPressClient } from "@/client/api.js";
|
|
|
3
3
|
import type { MCPToolSchema } from "@/types/mcp.js";
|
|
4
4
|
import { MediaQueryParams, UpdateMediaRequest, UploadMediaRequest } from "@/types/wordpress.js";
|
|
5
5
|
import { getErrorMessage } from "@/utils/error.js";
|
|
6
|
+
import { validateFilePath } from "@/utils/validation/security.js";
|
|
6
7
|
import { toolParams } from "./params.js";
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -223,10 +224,16 @@ export class MediaTools {
|
|
|
223
224
|
public async handleUploadMedia(client: WordPressClient, params: Record<string, unknown>): Promise<unknown> {
|
|
224
225
|
const uploadParams = toolParams<UploadMediaRequest & { file_path: string }>(params);
|
|
225
226
|
try {
|
|
227
|
+
// Validate file path to prevent path traversal attacks
|
|
228
|
+
// Set MCP_UPLOAD_BASE_DIR to restrict uploads to a specific directory (recommended in Docker)
|
|
229
|
+
const allowedBasePath = process.env.MCP_UPLOAD_BASE_DIR || "/";
|
|
230
|
+
const safePath = validateFilePath(uploadParams.file_path, allowedBasePath);
|
|
231
|
+
uploadParams.file_path = safePath;
|
|
232
|
+
|
|
226
233
|
try {
|
|
227
|
-
await fs.promises.access(
|
|
234
|
+
await fs.promises.access(safePath);
|
|
228
235
|
} catch (_error) {
|
|
229
|
-
throw new Error(`File not found at path: ${
|
|
236
|
+
throw new Error(`File not found at path: ${safePath}`);
|
|
230
237
|
}
|
|
231
238
|
|
|
232
239
|
const media = await client.uploadMedia(uploadParams);
|
package/src/tools/pages.ts
CHANGED
|
@@ -212,8 +212,21 @@ export class PageTools {
|
|
|
212
212
|
public async handleDeletePage(client: WordPressClient, params: Record<string, unknown>): Promise<unknown> {
|
|
213
213
|
const { id, force } = params as { id: number; force?: boolean };
|
|
214
214
|
try {
|
|
215
|
-
await client.deletePage(id, force);
|
|
216
|
-
const action =
|
|
215
|
+
const result = await client.deletePage(id, force);
|
|
216
|
+
const action = force ? "permanently deleted" : "moved to trash";
|
|
217
|
+
|
|
218
|
+
if (result?.deleted === false) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`WordPress refused to delete page ${id}. The page may be protected or the operation was rejected.`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (result?.deleted) {
|
|
225
|
+
const title = result.previous?.title?.rendered;
|
|
226
|
+
return title ? `✅ Page "${title}" has been ${action}.` : `✅ Page ${id} has been ${action}.`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Some WordPress installations return empty/null responses on successful deletion
|
|
217
230
|
return `✅ Page ${id} has been ${action}.`;
|
|
218
231
|
} catch (_error) {
|
|
219
232
|
throw new Error(`Failed to delete page: ${getErrorMessage(_error)}`);
|
|
@@ -99,7 +99,10 @@ export default class PerformanceTools {
|
|
|
99
99
|
return [
|
|
100
100
|
{
|
|
101
101
|
name: "wp_performance_stats",
|
|
102
|
-
description:
|
|
102
|
+
description:
|
|
103
|
+
"Get real-time performance statistics and metrics. " +
|
|
104
|
+
"Note: Top-level metrics (totalRequests, averageResponseTime, errorRate) are session-wide aggregates across all sites. " +
|
|
105
|
+
"Per-site cache and client stats are shown in the siteSpecific section when a site parameter is provided.",
|
|
103
106
|
parameters: [
|
|
104
107
|
{
|
|
105
108
|
name: "site",
|
|
@@ -156,7 +159,9 @@ export default class PerformanceTools {
|
|
|
156
159
|
},
|
|
157
160
|
{
|
|
158
161
|
name: "wp_performance_benchmark",
|
|
159
|
-
description:
|
|
162
|
+
description:
|
|
163
|
+
"Compare current performance against industry benchmarks. " +
|
|
164
|
+
"Note: Benchmarks are based on session-wide aggregated metrics across all sites, not per-site metrics.",
|
|
160
165
|
parameters: [
|
|
161
166
|
{
|
|
162
167
|
name: "site",
|
|
@@ -318,6 +323,7 @@ export default class PerformanceTools {
|
|
|
318
323
|
|
|
319
324
|
if (category === "overview" || category === "all") {
|
|
320
325
|
result.overview = {
|
|
326
|
+
scope: site ? "session-wide (all sites combined)" : "session-wide",
|
|
321
327
|
overallHealth: calculateHealthStatus(metrics),
|
|
322
328
|
performanceScore: calculatePerformanceScore(metrics),
|
|
323
329
|
totalRequests: metrics.requests.total,
|
|
@@ -237,8 +237,8 @@ export class SiteAuditor {
|
|
|
237
237
|
const posts = await this.client.getPosts({ per_page: this.config.maxPagesForContentAudit, status: ["publish"] });
|
|
238
238
|
const pages = await this.client.getPages({ per_page: this.config.maxPagesForContentAudit, status: ["publish"] });
|
|
239
239
|
|
|
240
|
-
// Get site
|
|
241
|
-
const siteUrl =
|
|
240
|
+
// Get site URL from the WordPress client configuration
|
|
241
|
+
const siteUrl = this.client.getSiteUrl();
|
|
242
242
|
|
|
243
243
|
return {
|
|
244
244
|
siteUrl,
|
|
@@ -599,9 +599,22 @@ export class SiteAuditor {
|
|
|
599
599
|
}
|
|
600
600
|
|
|
601
601
|
// Check for external dependencies (basic analysis)
|
|
602
|
+
let siteHostname: string;
|
|
603
|
+
try {
|
|
604
|
+
siteHostname = new URL(siteData.siteUrl).hostname;
|
|
605
|
+
} catch {
|
|
606
|
+
siteHostname = siteData.siteUrl.replace(/^https?:\/\//, "").replace(/[/:].*/g, "");
|
|
607
|
+
}
|
|
602
608
|
const externalDependencies = [...siteData.posts, ...siteData.pages].reduce((count, item) => {
|
|
603
609
|
const content = item.content?.rendered || "";
|
|
604
|
-
const externalLinks =
|
|
610
|
+
const externalLinks =
|
|
611
|
+
content.match(/https?:\/\/[^"'\s>]*/gi)?.filter((url) => {
|
|
612
|
+
try {
|
|
613
|
+
return new URL(url).hostname !== siteHostname;
|
|
614
|
+
} catch {
|
|
615
|
+
return true;
|
|
616
|
+
}
|
|
617
|
+
}) || [];
|
|
605
618
|
return count + externalLinks.length;
|
|
606
619
|
}, 0);
|
|
607
620
|
|
package/src/tools/site.ts
CHANGED
|
@@ -21,7 +21,8 @@ export class SiteTools {
|
|
|
21
21
|
return [
|
|
22
22
|
{
|
|
23
23
|
name: "wp_get_site_settings",
|
|
24
|
-
description:
|
|
24
|
+
description:
|
|
25
|
+
"Retrieves the general settings for a WordPress site. Requires administrator role (manage_options capability).",
|
|
25
26
|
inputSchema: {
|
|
26
27
|
type: "object",
|
|
27
28
|
properties: {},
|
|
@@ -30,7 +31,8 @@ export class SiteTools {
|
|
|
30
31
|
},
|
|
31
32
|
{
|
|
32
33
|
name: "wp_update_site_settings",
|
|
33
|
-
description:
|
|
34
|
+
description:
|
|
35
|
+
"Updates one or more general settings for a WordPress site. Requires administrator role (manage_options capability).",
|
|
34
36
|
inputSchema: {
|
|
35
37
|
type: "object",
|
|
36
38
|
properties: {
|
package/src/tools/users.ts
CHANGED
|
@@ -25,6 +25,8 @@ export class UserTools {
|
|
|
25
25
|
name: "wp_list_users",
|
|
26
26
|
description:
|
|
27
27
|
"Lists users from a WordPress site with comprehensive filtering and detailed user information including roles, registration dates, and activity status.\n\n" +
|
|
28
|
+
"**Note:** Role, email, and registration date fields require **administrator** privileges. " +
|
|
29
|
+
"Non-admin users will see limited metadata due to WordPress REST API restrictions.\n\n" +
|
|
28
30
|
"**Usage Examples:**\n" +
|
|
29
31
|
"• List all users: `wp_list_users`\n" +
|
|
30
32
|
'• Search users: `wp_list_users --search="john"`\n' +
|
|
@@ -206,16 +208,16 @@ export class UserTools {
|
|
|
206
208
|
month: "short",
|
|
207
209
|
day: "numeric",
|
|
208
210
|
})
|
|
209
|
-
: "
|
|
211
|
+
: "Restricted (requires admin)";
|
|
210
212
|
|
|
211
|
-
const roles = u.roles?.join(", ")
|
|
213
|
+
const roles = u.roles?.length ? u.roles.join(", ") : "Restricted (requires admin)";
|
|
212
214
|
const description = u.description || "No description";
|
|
213
215
|
const displayName = u.name || "No display name";
|
|
214
216
|
const userUrl = u.url || "No URL";
|
|
215
217
|
|
|
216
218
|
return (
|
|
217
219
|
`- **ID ${u.id}**: ${displayName} (@${u.slug})\n` +
|
|
218
|
-
` 📧 Email: ${u.email || "
|
|
220
|
+
` 📧 Email: ${u.email || "Restricted (requires admin)"}\n` +
|
|
219
221
|
` 🎭 Roles: ${roles}\n` +
|
|
220
222
|
` 📅 Registered: ${registrationDate}\n` +
|
|
221
223
|
` 🔗 URL: ${userUrl}\n` +
|