@adsim/wordpress-mcp-server 4.6.0 → 5.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.env.example +18 -0
  2. package/README.md +867 -499
  3. package/companion/mcp-diagnostics.php +1184 -0
  4. package/dxt/manifest.json +715 -98
  5. package/index.js +166 -4786
  6. package/package.json +14 -6
  7. package/src/data/plugin-performance-data.json +59 -0
  8. package/src/plugins/adapters/acf/acfAdapter.js +55 -3
  9. package/src/shared/api.js +79 -0
  10. package/src/shared/audit.js +39 -0
  11. package/src/shared/context.js +15 -0
  12. package/src/shared/governance.js +98 -0
  13. package/src/shared/utils.js +148 -0
  14. package/src/tools/comments.js +50 -0
  15. package/src/tools/content.js +395 -0
  16. package/src/tools/core.js +114 -0
  17. package/src/tools/editorial.js +634 -0
  18. package/src/tools/fse.js +370 -0
  19. package/src/tools/health.js +160 -0
  20. package/src/tools/index.js +96 -0
  21. package/src/tools/intelligence.js +2082 -0
  22. package/src/tools/links.js +118 -0
  23. package/src/tools/media.js +71 -0
  24. package/src/tools/performance.js +219 -0
  25. package/src/tools/plugins.js +368 -0
  26. package/src/tools/schema.js +417 -0
  27. package/src/tools/security.js +590 -0
  28. package/src/tools/seo.js +1633 -0
  29. package/src/tools/taxonomy.js +115 -0
  30. package/src/tools/users.js +188 -0
  31. package/src/tools/woocommerce.js +1008 -0
  32. package/src/tools/workflow.js +409 -0
  33. package/src/transport/http.js +39 -0
  34. package/tests/unit/helpers/pagination.test.js +43 -0
  35. package/tests/unit/plugins/acf/acfAdapter.test.js +43 -5
  36. package/tests/unit/tools/bulkUpdate.test.js +188 -0
  37. package/tests/unit/tools/diagnostics.test.js +397 -0
  38. package/tests/unit/tools/dynamicFiltering.test.js +100 -8
  39. package/tests/unit/tools/editorialIntelligence.test.js +817 -0
  40. package/tests/unit/tools/fse.test.js +548 -0
  41. package/tests/unit/tools/multilingual.test.js +653 -0
  42. package/tests/unit/tools/performance.test.js +351 -0
  43. package/tests/unit/tools/postMeta.test.js +105 -0
  44. package/tests/unit/tools/runWorkflow.test.js +150 -0
  45. package/tests/unit/tools/schema.test.js +477 -0
  46. package/tests/unit/tools/security.test.js +695 -0
  47. package/tests/unit/tools/site.test.js +1 -1
  48. package/tests/unit/tools/users.crud.test.js +399 -0
  49. package/tests/unit/tools/validateBlocks.test.js +186 -0
  50. package/tests/unit/tools/visualStaging.test.js +271 -0
  51. package/tests/unit/tools/woocommerce.advanced.test.js +679 -0
@@ -3,7 +3,7 @@ import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
3
3
  vi.mock('node-fetch', () => ({ default: vi.fn() }));
4
4
 
5
5
  import fetch from 'node-fetch';
6
- import { handleToolCall, getFilteredTools } from '../../../index.js';
6
+ import { handleToolCall, getFilteredTools, getEnabledCategories, TOOLS_DEFINITIONS } from '../../../index.js';
7
7
  import { makeRequest, mockSuccess, mockError, parseResult } from '../../helpers/mockWpRequest.js';
8
8
 
9
9
  function call(name, args = {}) {
@@ -26,11 +26,12 @@ function restoreEnv() {
26
26
  beforeEach(() => {
27
27
  fetch.mockReset();
28
28
  consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
29
- saveEnv('WC_CONSUMER_KEY', 'WP_REQUIRE_APPROVAL', 'WP_ENABLE_PLUGIN_INTELLIGENCE');
30
- // Default: all optional features OFF
29
+ saveEnv('WC_CONSUMER_KEY', 'WP_REQUIRE_APPROVAL', 'WP_ENABLE_PLUGIN_INTELLIGENCE', 'WP_TOOL_CATEGORIES');
30
+ // Default: all optional features OFF, no category filter
31
31
  delete process.env.WC_CONSUMER_KEY;
32
32
  delete process.env.WP_REQUIRE_APPROVAL;
33
33
  delete process.env.WP_ENABLE_PLUGIN_INTELLIGENCE;
34
+ delete process.env.WP_TOOL_CATEGORIES;
34
35
  });
35
36
  afterEach(() => {
36
37
  consoleSpy.mockRestore();
@@ -52,7 +53,7 @@ describe('WooCommerce filtering', () => {
52
53
  process.env.WC_CONSUMER_KEY = 'ck_test';
53
54
  const tools = getFilteredTools();
54
55
  const wcTools = tools.filter(t => t.name.startsWith('wc_'));
55
- expect(wcTools.length).toBe(13);
56
+ expect(wcTools.length).toBe(20);
56
57
  });
57
58
  });
58
59
 
@@ -106,15 +107,15 @@ describe('Plugin Intelligence filtering', () => {
106
107
  // =========================================================================
107
108
 
108
109
  describe('Combined filtering counts', () => {
109
- it('returns all 86 tools when all features enabled', () => {
110
+ it('returns all tools when all features enabled', () => {
110
111
  process.env.WC_CONSUMER_KEY = 'ck_test';
111
112
  process.env.WP_REQUIRE_APPROVAL = 'true';
112
113
  process.env.WP_ENABLE_PLUGIN_INTELLIGENCE = 'true';
113
- expect(getFilteredTools()).toHaveLength(86);
114
+ expect(getFilteredTools()).toHaveLength(176);
114
115
  });
115
116
 
116
- it('returns 64 tools with no optional features (86 - 13wc - 3editorial - 6pi)', () => {
117
- expect(getFilteredTools()).toHaveLength(64);
117
+ it('returns 145 tools with no optional features (174 - 20wc - 3editorial - 6pi)', () => {
118
+ expect(getFilteredTools()).toHaveLength(147);
118
119
  });
119
120
  });
120
121
 
@@ -134,3 +135,94 @@ describe('Filtered tools remain callable', () => {
134
135
  expect(res.content[0].text).not.toContain('Unknown tool');
135
136
  });
136
137
  });
138
+
139
+ // =========================================================================
140
+ // WP_TOOL_CATEGORIES filtering
141
+ // =========================================================================
142
+
143
+ describe('WP_TOOL_CATEGORIES filtering', () => {
144
+ it('WP_TOOL_CATEGORIES=seo → seo + core only', () => {
145
+ process.env.WP_TOOL_CATEGORIES = 'seo';
146
+ const tools = getFilteredTools();
147
+ const categories = [...new Set(tools.map(t => t._category))];
148
+ expect(categories).toContain('core');
149
+ expect(categories).toContain('seo');
150
+ expect(categories).not.toContain('content');
151
+ expect(categories).not.toContain('fse');
152
+ expect(tools.some(t => t.name === 'wp_audit_seo')).toBe(true);
153
+ });
154
+
155
+ it('_category is stripped from exposed tools (not present in returned objects)', () => {
156
+ process.env.WP_TOOL_CATEGORIES = 'seo';
157
+ const tools = getFilteredTools();
158
+ // _category IS present in getFilteredTools (internal)
159
+ expect(tools[0]._category).toBeDefined();
160
+ // But the ListTools handler strips it — we test the stripping logic directly
161
+ const exposed = tools.map(({ _category, ...tool }) => tool);
162
+ exposed.forEach(t => expect(t._category).toBeUndefined());
163
+ });
164
+
165
+ it('WP_TOOL_CATEGORIES=seo,content → union of seo + content + core', () => {
166
+ process.env.WP_TOOL_CATEGORIES = 'seo,content';
167
+ const tools = getFilteredTools();
168
+ const categories = [...new Set(tools.map(t => t._category))];
169
+ expect(categories).toContain('core');
170
+ expect(categories).toContain('seo');
171
+ expect(categories).toContain('content');
172
+ expect(categories).not.toContain('fse');
173
+ // No duplicates
174
+ const names = tools.map(t => t.name);
175
+ expect(names.length).toBe(new Set(names).size);
176
+ });
177
+
178
+ it('WP_TOOL_CATEGORIES=unknown_xyz → only core tools', () => {
179
+ process.env.WP_TOOL_CATEGORIES = 'unknown_xyz';
180
+ const tools = getFilteredTools();
181
+ const categories = [...new Set(tools.map(t => t._category))];
182
+ expect(categories).toEqual(['core']);
183
+ expect(tools.some(t => t.name === 'wp_site_info')).toBe(true);
184
+ expect(tools.some(t => t.name === 'wp_set_target')).toBe(true);
185
+ });
186
+
187
+ it('WP_TOOL_CATEGORIES= (empty string) → all tools exposed', () => {
188
+ process.env.WP_TOOL_CATEGORIES = '';
189
+ const tools = getFilteredTools();
190
+ // Same as default (no category filter) — 143 base tools
191
+ expect(tools).toHaveLength(147);
192
+ });
193
+
194
+ it('WP_TOOL_CATEGORIES undefined → all tools exposed', () => {
195
+ delete process.env.WP_TOOL_CATEGORIES;
196
+ const tools = getFilteredTools();
197
+ expect(tools).toHaveLength(147);
198
+ });
199
+
200
+ it('core tools (wp_site_info, wp_set_target) always present regardless of config', () => {
201
+ process.env.WP_TOOL_CATEGORIES = 'performance';
202
+ const tools = getFilteredTools();
203
+ const names = tools.map(t => t.name);
204
+ expect(names).toContain('wp_site_info');
205
+ expect(names).toContain('wp_set_target');
206
+ });
207
+
208
+ it('handleToolCall works for category-filtered-out tool', async () => {
209
+ process.env.WP_TOOL_CATEGORIES = 'seo';
210
+ const names = getFilteredTools().map(t => t.name);
211
+ expect(names).not.toContain('wp_list_posts');
212
+ // But calling it directly still works (not "Unknown tool")
213
+ fetch.mockImplementation(() => Promise.resolve({ ok: true, status: 200, headers: new Map([['x-wp-total', '0'], ['x-wp-totalpages', '0']]), json: () => Promise.resolve([]) }));
214
+ const res = await call('wp_list_posts');
215
+ expect(res.content[0].text).not.toContain('Unknown tool');
216
+ });
217
+
218
+ it('total TOOLS_DEFINITIONS count is 174', () => {
219
+ expect(TOOLS_DEFINITIONS).toHaveLength(176);
220
+ });
221
+
222
+ it('getEnabledCategories reflects WP_TOOL_CATEGORIES', () => {
223
+ process.env.WP_TOOL_CATEGORIES = 'seo,content';
224
+ expect(getEnabledCategories()).toEqual(['seo', 'content']);
225
+ delete process.env.WP_TOOL_CATEGORIES;
226
+ expect(getEnabledCategories()).toBeNull();
227
+ });
228
+ });