mcp-wordpress 1.5.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/README.md +332 -61
  2. package/dist/cache/CacheInvalidation.d.ts.map +1 -1
  3. package/dist/cache/CacheInvalidation.js +4 -4
  4. package/dist/cache/CacheInvalidation.js.map +1 -1
  5. package/dist/client/MockWordPressClient.d.ts +55 -0
  6. package/dist/client/MockWordPressClient.d.ts.map +1 -0
  7. package/dist/client/MockWordPressClient.js +369 -0
  8. package/dist/client/MockWordPressClient.js.map +1 -0
  9. package/dist/client/api.d.ts +1 -0
  10. package/dist/client/api.d.ts.map +1 -1
  11. package/dist/client/api.js +26 -60
  12. package/dist/client/api.js.map +1 -1
  13. package/dist/client/managers/AuthenticationManager.d.ts.map +1 -1
  14. package/dist/client/managers/AuthenticationManager.js +4 -3
  15. package/dist/client/managers/AuthenticationManager.js.map +1 -1
  16. package/dist/config/ConfigurationSchema.d.ts +3 -3
  17. package/dist/config/ConfigurationSchema.d.ts.map +1 -1
  18. package/dist/config/ConfigurationSchema.js +7 -24
  19. package/dist/config/ConfigurationSchema.js.map +1 -1
  20. package/dist/config/ServerConfiguration.d.ts +8 -0
  21. package/dist/config/ServerConfiguration.d.ts.map +1 -1
  22. package/dist/config/ServerConfiguration.js +80 -31
  23. package/dist/config/ServerConfiguration.js.map +1 -1
  24. package/dist/docs/DocumentationGenerator.d.ts.map +1 -1
  25. package/dist/docs/DocumentationGenerator.js +5 -7
  26. package/dist/docs/DocumentationGenerator.js.map +1 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +33 -29
  29. package/dist/index.js.map +1 -1
  30. package/dist/security/InputValidator.d.ts.map +1 -1
  31. package/dist/security/InputValidator.js +3 -11
  32. package/dist/security/InputValidator.js.map +1 -1
  33. package/dist/server/ToolRegistry.d.ts +4 -0
  34. package/dist/server/ToolRegistry.d.ts.map +1 -1
  35. package/dist/server/ToolRegistry.js +71 -8
  36. package/dist/server/ToolRegistry.js.map +1 -1
  37. package/dist/tools/auth.d.ts.map +1 -1
  38. package/dist/tools/auth.js +8 -3
  39. package/dist/tools/auth.js.map +1 -1
  40. package/dist/tools/posts.d.ts.map +1 -1
  41. package/dist/tools/posts.js +287 -20
  42. package/dist/tools/posts.js.map +1 -1
  43. package/dist/tools/site.d.ts.map +1 -1
  44. package/dist/tools/site.js +47 -9
  45. package/dist/tools/site.js.map +1 -1
  46. package/dist/tools/users.d.ts.map +1 -1
  47. package/dist/tools/users.js +113 -10
  48. package/dist/tools/users.js.map +1 -1
  49. package/dist/utils/enhancedError.d.ts +61 -0
  50. package/dist/utils/enhancedError.d.ts.map +1 -0
  51. package/dist/utils/enhancedError.js +221 -0
  52. package/dist/utils/enhancedError.js.map +1 -0
  53. package/dist/utils/streaming.d.ts +104 -0
  54. package/dist/utils/streaming.d.ts.map +1 -0
  55. package/dist/utils/streaming.js +312 -0
  56. package/dist/utils/streaming.js.map +1 -0
  57. package/dist/utils/validation.d.ts +19 -3
  58. package/dist/utils/validation.d.ts.map +1 -1
  59. package/dist/utils/validation.js +174 -24
  60. package/dist/utils/validation.js.map +1 -1
  61. package/docs/ARCHITECTURE.md +850 -0
  62. package/docs/CACHING.md +20 -17
  63. package/docs/CONFIGURATION.md +660 -0
  64. package/docs/DOCKER.md +61 -60
  65. package/docs/EVALUATION.md +397 -0
  66. package/docs/INSTALLATION.md +423 -0
  67. package/docs/PERFORMANCE_MONITORING.md +17 -15
  68. package/docs/SECURITY.md +621 -0
  69. package/docs/SECURITY_TESTING.md +22 -26
  70. package/docs/TEST_SITE_SETUP.md +136 -0
  71. package/docs/TROUBLESHOOTING.md +578 -0
  72. package/docs/api/README.md +76 -91
  73. package/docs/api/categories/auth.md +0 -2
  74. package/docs/api/categories/cache.md +0 -2
  75. package/docs/api/categories/comment.md +0 -2
  76. package/docs/api/categories/media.md +0 -2
  77. package/docs/api/categories/page.md +0 -2
  78. package/docs/api/categories/performance.md +0 -2
  79. package/docs/api/categories/post.md +0 -2
  80. package/docs/api/categories/site.md +0 -2
  81. package/docs/api/categories/taxonomy.md +0 -2
  82. package/docs/api/categories/user.md +0 -2
  83. package/docs/api/summary.json +1 -1
  84. package/docs/api/tools/wp_approve_comment.md +11 -3
  85. package/docs/api/tools/wp_cache_clear.md +14 -5
  86. package/docs/api/tools/wp_cache_info.md +14 -5
  87. package/docs/api/tools/wp_cache_stats.md +14 -5
  88. package/docs/api/tools/wp_cache_warm.md +14 -5
  89. package/docs/api/tools/wp_create_application_password.md +11 -3
  90. package/docs/api/tools/wp_create_category.md +11 -3
  91. package/docs/api/tools/wp_create_comment.md +14 -5
  92. package/docs/api/tools/wp_create_page.md +13 -5
  93. package/docs/api/tools/wp_create_post.md +14 -7
  94. package/docs/api/tools/wp_create_tag.md +11 -3
  95. package/docs/api/tools/wp_create_user.md +13 -5
  96. package/docs/api/tools/wp_delete_application_password.md +11 -3
  97. package/docs/api/tools/wp_delete_category.md +11 -3
  98. package/docs/api/tools/wp_delete_comment.md +11 -3
  99. package/docs/api/tools/wp_delete_media.md +10 -3
  100. package/docs/api/tools/wp_delete_page.md +10 -3
  101. package/docs/api/tools/wp_delete_post.md +11 -5
  102. package/docs/api/tools/wp_delete_tag.md +11 -3
  103. package/docs/api/tools/wp_delete_user.md +10 -3
  104. package/docs/api/tools/wp_get_application_passwords.md +11 -3
  105. package/docs/api/tools/wp_get_auth_status.md +11 -3
  106. package/docs/api/tools/wp_get_category.md +11 -3
  107. package/docs/api/tools/wp_get_comment.md +11 -3
  108. package/docs/api/tools/wp_get_current_user.md +11 -3
  109. package/docs/api/tools/wp_get_media.md +11 -3
  110. package/docs/api/tools/wp_get_page.md +11 -3
  111. package/docs/api/tools/wp_get_page_revisions.md +11 -3
  112. package/docs/api/tools/wp_get_post.md +12 -5
  113. package/docs/api/tools/wp_get_post_revisions.md +11 -3
  114. package/docs/api/tools/wp_get_site_settings.md +10 -3
  115. package/docs/api/tools/wp_get_tag.md +11 -3
  116. package/docs/api/tools/wp_get_user.md +11 -3
  117. package/docs/api/tools/wp_list_categories.md +11 -3
  118. package/docs/api/tools/wp_list_comments.md +11 -3
  119. package/docs/api/tools/wp_list_media.md +14 -5
  120. package/docs/api/tools/wp_list_pages.md +14 -5
  121. package/docs/api/tools/wp_list_posts.md +15 -7
  122. package/docs/api/tools/wp_list_tags.md +11 -3
  123. package/docs/api/tools/wp_list_users.md +11 -3
  124. package/docs/api/tools/wp_performance_alerts.md +17 -7
  125. package/docs/api/tools/wp_performance_benchmark.md +17 -7
  126. package/docs/api/tools/wp_performance_export.md +17 -7
  127. package/docs/api/tools/wp_performance_history.md +17 -7
  128. package/docs/api/tools/wp_performance_optimize.md +17 -7
  129. package/docs/api/tools/wp_performance_stats.md +17 -7
  130. package/docs/api/tools/wp_search_site.md +11 -3
  131. package/docs/api/tools/wp_spam_comment.md +11 -3
  132. package/docs/api/tools/wp_switch_auth_method.md +14 -5
  133. package/docs/api/tools/wp_test_auth.md +11 -3
  134. package/docs/api/tools/wp_update_category.md +11 -3
  135. package/docs/api/tools/wp_update_comment.md +14 -5
  136. package/docs/api/tools/wp_update_media.md +14 -5
  137. package/docs/api/tools/wp_update_page.md +13 -5
  138. package/docs/api/tools/wp_update_post.md +14 -7
  139. package/docs/api/tools/wp_update_site_settings.md +14 -5
  140. package/docs/api/tools/wp_update_tag.md +11 -3
  141. package/docs/api/tools/wp_update_user.md +13 -5
  142. package/docs/api/tools/wp_upload_media.md +13 -5
  143. package/docs/api/types/WordPressPost.md +2 -0
  144. package/docs/code-improvements.md +40 -0
  145. package/docs/contract-testing.md +1 -1
  146. package/docs/developer/API_REFERENCE.md +19 -59
  147. package/docs/developer/ARCHITECTURE.md +8 -11
  148. package/docs/developer/BUILD_SYSTEM.md +2 -2
  149. package/docs/developer/CONTRIBUTING.md +3 -5
  150. package/docs/developer/GITHUB_ACTIONS_SETUP.md +2 -2
  151. package/docs/developer/MIGRATION_GUIDE.md +5 -6
  152. package/docs/developer/README.md +2 -1
  153. package/docs/developer/REFACTORING.md +9 -15
  154. package/docs/developer/RELEASE_PROCESS.md +4 -3
  155. package/docs/developer/TESTING.md +2 -2
  156. package/docs/examples/claude-desktop-config.md +8 -0
  157. package/docs/integrations/claude-desktop.md +426 -0
  158. package/docs/integrations/cline.md +537 -0
  159. package/docs/integrations/vs-code.md +515 -0
  160. package/docs/releases/COMMUNITY_ANNOUNCEMENT_v1.1.2.md +30 -23
  161. package/docs/releases/RELEASE_NOTES_v1.1.2.md +7 -6
  162. package/docs/testing-configurations.md +11 -0
  163. package/docs/user-guides/DOCKER_NPM_DTX_SETUP.md +3 -2
  164. package/docs/user-guides/DOCKER_SETUP.md +3 -2
  165. package/docs/user-guides/DTX_SETUP.md +6 -5
  166. package/docs/user-guides/DXT_INSTALLATION.md +4 -4
  167. package/docs/user-guides/NPM_SETUP.md +4 -2
  168. package/docs/user-guides/NPX_SETUP.md +4 -2
  169. package/docs/user-guides/SMITHERY_SETUP.md +402 -0
  170. package/docs/wordpress-rest-api-authentication-troubleshooting.md +45 -42
  171. package/package.json +12 -2
  172. package/src/cache/CacheInvalidation.ts +7 -18
  173. package/src/client/MockWordPressClient.ts +398 -0
  174. package/src/client/api.ts +77 -237
  175. package/src/client/managers/AuthenticationManager.ts +19 -56
  176. package/src/config/ConfigurationSchema.ts +14 -45
  177. package/src/config/ServerConfiguration.ts +98 -71
  178. package/src/docs/DocumentationGenerator.ts +39 -123
  179. package/src/dxt-entry.cjs +4 -1
  180. package/src/index.ts +35 -54
  181. package/src/security/InputValidator.ts +15 -57
  182. package/src/server/ToolRegistry.ts +88 -17
  183. package/src/tools/auth.ts +15 -22
  184. package/src/tools/posts.ts +347 -64
  185. package/src/tools/site.ts +69 -46
  186. package/src/tools/users.ts +142 -44
  187. package/src/utils/enhancedError.ts +248 -0
  188. package/src/utils/streaming.ts +428 -0
  189. package/src/utils/validation.ts +253 -92
  190. package/dist/mcp-wordpress-1.5.2.tgz +0 -0
@@ -1,10 +1,9 @@
1
1
  import { WordPressClient } from "../client/api.js";
2
- import {
3
- CreatePostRequest,
4
- PostQueryParams,
5
- UpdatePostRequest,
6
- } from "../types/wordpress.js";
2
+ import { CreatePostRequest, PostQueryParams, UpdatePostRequest } from "../types/wordpress.js";
7
3
  import { getErrorMessage } from "../utils/error.js";
4
+ import { ErrorHandlers, EnhancedError } from "../utils/enhancedError.js";
5
+ import { validateId, validatePaginationParams, validatePostParams } from "../utils/validation.js";
6
+ import { WordPressDataStreamer, StreamingUtils, StreamingResult } from "../utils/streaming.js";
8
7
 
9
8
  /**
10
9
  * Provides tools for managing posts on a WordPress site.
@@ -19,7 +18,15 @@ export class PostTools {
19
18
  return [
20
19
  {
21
20
  name: "wp_list_posts",
22
- description: "Lists posts from a WordPress site, with filters.",
21
+ description:
22
+ "Lists posts from a WordPress site with comprehensive filtering options. Supports search, status filtering, and category/tag filtering with enhanced metadata display.\n\n" +
23
+ "**Usage Examples:**\n" +
24
+ "• Basic listing: `wp_list_posts`\n" +
25
+ '• Search posts: `wp_list_posts --search="AI trends"`\n' +
26
+ '• Filter by status: `wp_list_posts --status="draft"`\n' +
27
+ "• Category filtering: `wp_list_posts --categories=[1,2,3]`\n" +
28
+ "• Paginated results: `wp_list_posts --per_page=20 --page=2`\n" +
29
+ '• Combined filters: `wp_list_posts --search="WordPress" --status="publish" --per_page=10`',
23
30
  parameters: [
24
31
  {
25
32
  name: "per_page",
@@ -54,7 +61,8 @@ export class PostTools {
54
61
  },
55
62
  {
56
63
  name: "wp_get_post",
57
- description: "Retrieves a single post by its ID.",
64
+ description:
65
+ "Retrieves detailed information about a single post including metadata, content statistics, and management links.",
58
66
  parameters: [
59
67
  {
60
68
  name: "id",
@@ -67,7 +75,14 @@ export class PostTools {
67
75
  },
68
76
  {
69
77
  name: "wp_create_post",
70
- description: "Creates a new post.",
78
+ description:
79
+ "Creates a new WordPress post with comprehensive validation and detailed success feedback including management links.\n\n" +
80
+ "**Usage Examples:**\n" +
81
+ '• Simple post: `wp_create_post --title="My New Post" --content="<p>Hello World!</p>"`\n' +
82
+ '• Draft post: `wp_create_post --title="Draft Post" --status="draft"`\n' +
83
+ '• Categorized post: `wp_create_post --title="Tech News" --categories=[1,5] --tags=[10,20]`\n' +
84
+ '• Scheduled post: `wp_create_post --title="Future Post" --status="future" --date="2024-12-25T10:00:00"`\n' +
85
+ '• Complete post: `wp_create_post --title="Complete Post" --content="<p>Content</p>" --excerpt="Summary" --status="publish"`',
71
86
  parameters: [
72
87
  {
73
88
  name: "title",
@@ -108,7 +123,7 @@ export class PostTools {
108
123
  },
109
124
  {
110
125
  name: "wp_update_post",
111
- description: "Updates an existing post.",
126
+ description: "Updates an existing WordPress post with validation and detailed confirmation.",
112
127
  parameters: [
113
128
  {
114
129
  name: "id",
@@ -137,7 +152,7 @@ export class PostTools {
137
152
  },
138
153
  {
139
154
  name: "wp_delete_post",
140
- description: "Deletes a post.",
155
+ description: "Deletes a WordPress post with option for permanent deletion or moving to trash.",
141
156
  parameters: [
142
157
  {
143
158
  name: "id",
@@ -148,15 +163,14 @@ export class PostTools {
148
163
  {
149
164
  name: "force",
150
165
  type: "boolean",
151
- description:
152
- "If true, permanently delete. If false, move to trash. Defaults to false.",
166
+ description: "If true, permanently delete. If false, move to trash. Defaults to false.",
153
167
  },
154
168
  ],
155
169
  handler: this.handleDeletePost.bind(this),
156
170
  },
157
171
  {
158
172
  name: "wp_get_post_revisions",
159
- description: "Retrieves revisions for a specific post.",
173
+ description: "Retrieves the revision history for a specific post showing author and modification dates.",
160
174
  parameters: [
161
175
  {
162
176
  name: "id",
@@ -170,63 +184,345 @@ export class PostTools {
170
184
  ];
171
185
  }
172
186
 
173
- public async handleListPosts(
174
- client: WordPressClient,
175
- params: PostQueryParams,
176
- ): Promise<any> {
187
+ public async handleListPosts(client: WordPressClient, params: PostQueryParams): Promise<any> {
177
188
  try {
178
- const posts = await client.getPosts(params);
189
+ // Enhanced input validation and sanitization
190
+ const paginationValidated = validatePaginationParams({
191
+ page: params.page,
192
+ per_page: params.per_page,
193
+ offset: params.offset,
194
+ });
195
+
196
+ const sanitizedParams = {
197
+ ...params,
198
+ ...paginationValidated,
199
+ };
200
+
201
+ // Validate and sanitize search term
202
+ if (sanitizedParams.search) {
203
+ sanitizedParams.search = sanitizedParams.search.trim();
204
+ if (sanitizedParams.search.length === 0) {
205
+ delete sanitizedParams.search;
206
+ }
207
+ }
208
+
209
+ // Validate category and tag IDs if provided
210
+ if (sanitizedParams.categories) {
211
+ sanitizedParams.categories = sanitizedParams.categories.map((id) => validateId(id, "category ID"));
212
+ }
213
+
214
+ if (sanitizedParams.tags) {
215
+ sanitizedParams.tags = sanitizedParams.tags.map((id) => validateId(id, "tag ID"));
216
+ }
217
+
218
+ // Validate status parameter
219
+ if (sanitizedParams.status) {
220
+ const validStatuses = ["publish", "future", "draft", "pending", "private"];
221
+ const statusesToCheck = Array.isArray(sanitizedParams.status)
222
+ ? sanitizedParams.status
223
+ : [sanitizedParams.status];
224
+
225
+ for (const statusToCheck of statusesToCheck) {
226
+ if (!validStatuses.includes(statusToCheck)) {
227
+ throw ErrorHandlers.validationError("status", statusToCheck, "one of: " + validStatuses.join(", "));
228
+ }
229
+ }
230
+ }
231
+
232
+ // Performance optimization: set reasonable defaults
233
+ if (!sanitizedParams.per_page) {
234
+ sanitizedParams.per_page = 10; // Default to 10 posts for better performance
235
+ }
236
+
237
+ const posts = await client.getPosts(sanitizedParams);
179
238
  if (posts.length === 0) {
180
- return "No posts found matching the criteria.";
239
+ const searchInfo = sanitizedParams.search ? ` matching "${sanitizedParams.search}"` : "";
240
+ const statusInfo = sanitizedParams.status ? ` with status "${sanitizedParams.status}"` : "";
241
+ return `No posts found${searchInfo}${statusInfo}. Try adjusting your search criteria or check if posts exist.`;
242
+ }
243
+
244
+ // Use streaming for large result sets (>50 posts)
245
+ if (posts.length > 50) {
246
+ const streamResults: StreamingResult<any>[] = [];
247
+
248
+ for await (const result of WordPressDataStreamer.streamPosts(posts, {
249
+ includeAuthor: true,
250
+ includeCategories: true,
251
+ includeTags: true,
252
+ batchSize: 20,
253
+ })) {
254
+ streamResults.push(result);
255
+ }
256
+
257
+ return StreamingUtils.formatStreamingResponse(streamResults, "posts");
181
258
  }
259
+
260
+ // Add comprehensive site context information
261
+ const siteUrl = client.getSiteUrl ? client.getSiteUrl() : "Unknown site";
262
+ const totalPosts = posts.length;
263
+ const statusCounts = posts.reduce(
264
+ (acc, p) => {
265
+ acc[p.status] = (acc[p.status] || 0) + 1;
266
+ return acc;
267
+ },
268
+ {} as Record<string, number>,
269
+ );
270
+
271
+ // Enhanced metadata
272
+ const metadata = [
273
+ `📊 **Posts Summary**: ${totalPosts} total`,
274
+ `📝 **Status Breakdown**: ${Object.entries(statusCounts)
275
+ .map(([status, count]) => `${status}: ${count}`)
276
+ .join(", ")}`,
277
+ `🌐 **Source**: ${siteUrl}`,
278
+ `📅 **Retrieved**: ${new Date().toLocaleString()}`,
279
+ ...(params.search ? [`🔍 **Search Term**: "${params.search}"`] : []),
280
+ ...(params.categories ? [`📁 **Categories**: ${params.categories.join(", ")}`] : []),
281
+ ...(params.tags ? [`🏷️ **Tags**: ${params.tags.join(", ")}`] : []),
282
+ ];
283
+
284
+ // Fetch additional metadata for enhanced responses
285
+ const authorIds = [...new Set(posts.map((p) => p.author).filter(Boolean))];
286
+ const categoryIds = [...new Set(posts.flatMap((p) => p.categories || []))];
287
+ const tagIds = [...new Set(posts.flatMap((p) => p.tags || []))];
288
+
289
+ // Fetch authors, categories, and tags in parallel for better performance
290
+ const [authors, categories, tags] = await Promise.all([
291
+ authorIds.length > 0
292
+ ? Promise.all(
293
+ authorIds.map(async (id) => {
294
+ try {
295
+ const user = await client.getUser(id);
296
+ return { id, name: user.name || user.username || `User ${id}` };
297
+ } catch {
298
+ return { id, name: `User ${id}` };
299
+ }
300
+ }),
301
+ )
302
+ : [],
303
+ categoryIds.length > 0
304
+ ? Promise.all(
305
+ categoryIds.map(async (id) => {
306
+ try {
307
+ const category = await client.getCategory(id);
308
+ return { id, name: category.name || `Category ${id}` };
309
+ } catch {
310
+ return { id, name: `Category ${id}` };
311
+ }
312
+ }),
313
+ )
314
+ : [],
315
+ tagIds.length > 0
316
+ ? Promise.all(
317
+ tagIds.map(async (id) => {
318
+ try {
319
+ const tag = await client.getTag(id);
320
+ return { id, name: tag.name || `Tag ${id}` };
321
+ } catch {
322
+ return { id, name: `Tag ${id}` };
323
+ }
324
+ }),
325
+ )
326
+ : [],
327
+ ]);
328
+
329
+ // Create lookup maps for performance
330
+ const authorMap = new Map(authors.map((a) => [a.id, a.name]));
331
+ const categoryMap = new Map(categories.map((c) => [c.id, c.name]));
332
+ const tagMap = new Map(tags.map((t) => [t.id, t.name]));
333
+
182
334
  const content =
183
- `Found ${posts.length} posts:\n\n` +
335
+ metadata.join("\n") +
336
+ "\n\n" +
184
337
  posts
185
- .map(
186
- (p) =>
187
- `- ID ${p.id}: **${p.title.rendered}** (${p.status})\n Link: ${p.link}`,
188
- )
189
- .join("\n");
190
- return content;
338
+ .map((p) => {
339
+ const date = new Date(p.date);
340
+ const formattedDate = date.toLocaleDateString("en-US", {
341
+ year: "numeric",
342
+ month: "short",
343
+ day: "numeric",
344
+ });
345
+ const excerpt = p.excerpt?.rendered
346
+ ? p.excerpt.rendered.replace(/<[^>]*>/g, "").substring(0, 80) + "..."
347
+ : "";
348
+
349
+ // Enhanced metadata
350
+ const authorName = authorMap.get(p.author) || `User ${p.author}`;
351
+ const postCategories = (p.categories || []).map((id) => categoryMap.get(id) || `Category ${id}`);
352
+ const postTags = (p.tags || []).map((id) => tagMap.get(id) || `Tag ${id}`);
353
+
354
+ let postInfo = `- ID ${p.id}: **${p.title.rendered}** (${p.status})\n`;
355
+ postInfo += ` 👤 Author: ${authorName}\n`;
356
+ postInfo += ` 📅 Published: ${formattedDate}\n`;
357
+ if (postCategories.length > 0) {
358
+ postInfo += ` 📁 Categories: ${postCategories.join(", ")}\n`;
359
+ }
360
+ if (postTags.length > 0) {
361
+ postInfo += ` 🏷️ Tags: ${postTags.join(", ")}\n`;
362
+ }
363
+ if (excerpt) {
364
+ postInfo += ` 📝 Excerpt: ${excerpt}\n`;
365
+ }
366
+ postInfo += ` 🔗 Link: ${p.link}`;
367
+
368
+ return postInfo;
369
+ })
370
+ .join("\n\n");
371
+
372
+ // Add pagination guidance for large result sets
373
+ let finalContent = content;
374
+ if (posts.length >= (sanitizedParams.per_page || 10)) {
375
+ finalContent += `\n\n📄 **Pagination Tip**: Use \`per_page\` parameter to control results (max 100). Current: ${sanitizedParams.per_page || 10}`;
376
+ }
377
+
378
+ return finalContent;
191
379
  } catch (error) {
192
380
  throw new Error(`Failed to list posts: ${getErrorMessage(error)}`);
193
381
  }
194
382
  }
195
383
 
196
- public async handleGetPost(
197
- client: WordPressClient,
198
- params: { id: number },
199
- ): Promise<any> {
384
+ public async handleGetPost(client: WordPressClient, params: { id: number }): Promise<any> {
200
385
  try {
386
+ // Input validation
387
+ if (!params.id || typeof params.id !== "number" || params.id <= 0) {
388
+ throw ErrorHandlers.validationError("id", params.id, "positive integer");
389
+ }
390
+
201
391
  const post = await client.getPost(params.id);
202
- const content =
203
- `**Post Details (ID: ${post.id})**\n\n` +
204
- `- **Title:** ${post.title.rendered}\n` +
205
- `- **Status:** ${post.status}\n` +
206
- `- **Link:** ${post.link}\n` +
207
- `- **Date:** ${new Date(post.date).toLocaleString()}`;
392
+
393
+ // Fetch additional metadata for enhanced response
394
+ const [author, categories, tags] = await Promise.all([
395
+ post.author
396
+ ? (async () => {
397
+ try {
398
+ const user = await client.getUser(post.author);
399
+ return user.name || user.username || `User ${post.author}`;
400
+ } catch {
401
+ return `User ${post.author}`;
402
+ }
403
+ })()
404
+ : "Unknown",
405
+ post.categories && post.categories.length > 0
406
+ ? Promise.all(
407
+ post.categories.map(async (id) => {
408
+ try {
409
+ const category = await client.getCategory(id);
410
+ return category.name || `Category ${id}`;
411
+ } catch {
412
+ return `Category ${id}`;
413
+ }
414
+ }),
415
+ )
416
+ : [],
417
+ post.tags && post.tags.length > 0
418
+ ? Promise.all(
419
+ post.tags.map(async (id) => {
420
+ try {
421
+ const tag = await client.getTag(id);
422
+ return tag.name || `Tag ${id}`;
423
+ } catch {
424
+ return `Tag ${id}`;
425
+ }
426
+ }),
427
+ )
428
+ : [],
429
+ ]);
430
+
431
+ // Enhanced post details with comprehensive metadata
432
+ const siteUrl = client.getSiteUrl ? client.getSiteUrl() : "Unknown site";
433
+ const publishedDate = new Date(post.date);
434
+ const modifiedDate = new Date(post.modified);
435
+ const excerpt = post.excerpt?.rendered
436
+ ? post.excerpt.rendered.replace(/<[^>]*>/g, "").substring(0, 150) + "..."
437
+ : "No excerpt available";
438
+ const wordCount = post.content?.rendered ? post.content.rendered.replace(/<[^>]*>/g, "").split(/\s+/).length : 0;
439
+
440
+ let content = `**📄 Post Details (ID: ${post.id})**\n\n`;
441
+ content += `**📋 Basic Information:**\n`;
442
+ content += `- **Title:** ${post.title.rendered}\n`;
443
+ content += `- **Status:** ${post.status}\n`;
444
+ content += `- **Type:** ${post.type}\n`;
445
+ content += `- **Author:** ${author}\n`;
446
+ content += `- **Slug:** ${post.slug}\n\n`;
447
+
448
+ content += `**📅 Dates:**\n`;
449
+ content += `- **Published:** ${publishedDate.toLocaleString()}\n`;
450
+ content += `- **Modified:** ${modifiedDate.toLocaleString()}\n\n`;
451
+
452
+ content += `**📊 Content:**\n`;
453
+ content += `- **Word Count:** ~${wordCount} words\n`;
454
+ content += `- **Excerpt:** ${excerpt}\n\n`;
455
+
456
+ if (categories.length > 0) {
457
+ content += `**📁 Categories:** ${categories.join(", ")}\n`;
458
+ }
459
+ if (tags.length > 0) {
460
+ content += `**🏷️ Tags:** ${tags.join(", ")}\n`;
461
+ }
462
+ if (categories.length > 0 || tags.length > 0) {
463
+ content += `\n`;
464
+ }
465
+
466
+ content += `**🔗 Links:**\n`;
467
+ content += `- **Permalink:** ${post.link}\n`;
468
+ content += `- **Edit Link:** ${post.link.replace(/\/$/, "")}/wp-admin/post.php?post=${post.id}&action=edit\n\n`;
469
+ content += `**🌐 Source:** ${siteUrl}\n`;
470
+ content += `**📅 Retrieved:** ${new Date().toLocaleString()}`;
471
+
208
472
  return content;
209
473
  } catch (error) {
210
- throw new Error(`Failed to get post: ${getErrorMessage(error)}`);
474
+ // Handle specific error cases
475
+ const errorMessage = getErrorMessage(error);
476
+
477
+ if (errorMessage.includes("Invalid post ID") || errorMessage.includes("not found")) {
478
+ throw ErrorHandlers.postNotFound(params.id, error);
479
+ }
480
+
481
+ if (errorMessage.includes("401") || errorMessage.includes("Unauthorized")) {
482
+ throw ErrorHandlers.authenticationFailed(error);
483
+ }
484
+
485
+ if (errorMessage.includes("403") || errorMessage.includes("Forbidden")) {
486
+ throw ErrorHandlers.permissionDenied("get post", error);
487
+ }
488
+
489
+ if (errorMessage.includes("timeout") || errorMessage.includes("network")) {
490
+ throw ErrorHandlers.connectionError(error);
491
+ }
492
+
493
+ // Generic error with suggestions
494
+ throw ErrorHandlers.generic("get post", error);
211
495
  }
212
496
  }
213
497
 
214
- public async handleCreatePost(
215
- client: WordPressClient,
216
- params: CreatePostRequest,
217
- ): Promise<any> {
498
+ public async handleCreatePost(client: WordPressClient, params: CreatePostRequest): Promise<any> {
218
499
  try {
219
- const post = await client.createPost(params);
220
- return `✅ Post created successfully!\n- ID: ${post.id}\n- Title: ${post.title.rendered}\n- Link: ${post.link}`;
500
+ // Enhanced input validation using new validation utilities
501
+ const validatedParams = validatePostParams(params);
502
+
503
+ const post = await client.createPost(validatedParams);
504
+ const siteUrl = client.getSiteUrl ? client.getSiteUrl() : "Unknown site";
505
+
506
+ return (
507
+ `✅ **Post Created Successfully!**\n\n` +
508
+ `**📄 Post Details:**\n` +
509
+ `- **ID:** ${post.id}\n` +
510
+ `- **Title:** ${post.title.rendered}\n` +
511
+ `- **Status:** ${post.status}\n` +
512
+ `- **Link:** ${post.link}\n` +
513
+ `- **Edit Link:** ${post.link.replace(/\/$/, "")}/wp-admin/post.php?post=${post.id}&action=edit\n\n` +
514
+ `**🌐 Site:** ${siteUrl}\n` +
515
+ `**📅 Created:** ${new Date().toLocaleString()}`
516
+ );
221
517
  } catch (error) {
518
+ if (error instanceof EnhancedError) {
519
+ throw error;
520
+ }
222
521
  throw new Error(`Failed to create post: ${getErrorMessage(error)}`);
223
522
  }
224
523
  }
225
524
 
226
- public async handleUpdatePost(
227
- client: WordPressClient,
228
- params: UpdatePostRequest & { id: number },
229
- ): Promise<any> {
525
+ public async handleUpdatePost(client: WordPressClient, params: UpdatePostRequest & { id: number }): Promise<any> {
230
526
  try {
231
527
  const post = await client.updatePost(params);
232
528
  return `✅ Post ${post.id} updated successfully.`;
@@ -235,10 +531,7 @@ export class PostTools {
235
531
  }
236
532
  }
237
533
 
238
- public async handleDeletePost(
239
- client: WordPressClient,
240
- params: { id: number; force?: boolean },
241
- ): Promise<any> {
534
+ public async handleDeletePost(client: WordPressClient, params: { id: number; force?: boolean }): Promise<any> {
242
535
  try {
243
536
  await client.deletePost(params.id, params.force);
244
537
  const action = params.force ? "permanently deleted" : "moved to trash";
@@ -248,10 +541,7 @@ export class PostTools {
248
541
  }
249
542
  }
250
543
 
251
- public async handleGetPostRevisions(
252
- client: WordPressClient,
253
- params: { id: number },
254
- ): Promise<any> {
544
+ public async handleGetPostRevisions(client: WordPressClient, params: { id: number }): Promise<any> {
255
545
  try {
256
546
  const revisions = await client.getPostRevisions(params.id);
257
547
  if (revisions.length === 0) {
@@ -260,18 +550,11 @@ export class PostTools {
260
550
  const content =
261
551
  `Found ${revisions.length} revisions for post ${params.id}:\n\n` +
262
552
  revisions
263
- .map(
264
- (r) =>
265
- `- Revision by user ID ${r.author} at ${new Date(
266
- r.modified,
267
- ).toLocaleString()}`,
268
- )
553
+ .map((r) => `- Revision by user ID ${r.author} at ${new Date(r.modified).toLocaleString()}`)
269
554
  .join("\n");
270
555
  return content;
271
556
  } catch (error) {
272
- throw new Error(
273
- `Failed to get post revisions: ${getErrorMessage(error)}`,
274
- );
557
+ throw new Error(`Failed to get post revisions: ${getErrorMessage(error)}`);
275
558
  }
276
559
  }
277
560
  }