@wplaunchify/ml-mcp-server 2.5.5 → 2.5.7

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/build/server.js CHANGED
@@ -5,7 +5,6 @@ import * as dotenv from 'dotenv';
5
5
  dotenv.config({ path: '.env' });
6
6
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
7
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
- import { allTools, toolHandlers } from './tools/index.js';
9
8
  import { z } from 'zod';
10
9
  // Generate server name from WordPress URL
11
10
  function generateServerName() {
@@ -40,66 +39,50 @@ function generateServerName() {
40
39
  return 'wordpress';
41
40
  }
42
41
  }
43
- // Create MCP server instance
44
- const server = new McpServer({
45
- name: generateServerName(),
46
- version: "1.0.7"
47
- }, {
48
- capabilities: {
49
- tools: allTools.reduce((acc, tool) => {
50
- acc[tool.name] = tool;
51
- return acc;
52
- }, {})
53
- }
54
- });
55
- // Register each tool from our tools list with its corresponding handler
56
- let registeredCount = 0;
57
- for (const tool of allTools) {
58
- const handler = toolHandlers[tool.name];
59
- if (!handler) {
60
- console.error(`⚠️ No handler for tool: ${tool.name}`);
61
- continue;
62
- }
63
- const wrappedHandler = async (args) => {
64
- try {
65
- // The handler functions are already typed with their specific parameter types
66
- const result = await handler(args);
67
- return {
68
- content: result.toolResult.content.map((item) => ({
69
- ...item,
70
- type: "text"
71
- })),
72
- isError: result.toolResult.isError
73
- };
74
- }
75
- catch (error) {
76
- // Return error as tool result instead of throwing
77
- return {
78
- content: [{
79
- type: "text",
80
- text: `Error executing ${tool.name}: ${error.message || String(error)}`
81
- }],
82
- isError: true
83
- };
42
+ /**
43
+ * Register tools with the MCP server
44
+ * This function is called after async tool loading completes
45
+ */
46
+ function registerTools(server, loadedTools, handlers) {
47
+ let registeredCount = 0;
48
+ for (const tool of loadedTools) {
49
+ const handler = handlers[tool.name];
50
+ if (!handler) {
51
+ console.error(`⚠️ No handler for tool: ${tool.name}`);
52
+ continue;
84
53
  }
85
- };
86
- // console.log(`Registering tool: ${tool.name}`);
87
- // console.log(`Input schema: ${JSON.stringify(tool.inputSchema)}`);
88
- // const zodSchema = z.any().optional();
89
- // const jsonSchema = zodToJsonSchema(z.object(tool.inputSchema.properties as z.ZodRawShape));
90
- // const schema = z.object(tool.inputSchema as z.ZodRawShape).catchall(z.unknown());
91
- // The inputSchema is already in JSON Schema format with properties
92
- // server.tool(tool.name, tool.inputSchema.shape, wrappedHandler);
93
- // const zodSchema = z.any().optional();
94
- // const jsonSchema = zodToJsonSchema(z.object(tool.inputSchema.properties as z.ZodRawShape));
95
- // const parsedSchema = z.any().optional().parse(jsonSchema);
96
- const zodSchema = z.object(tool.inputSchema.properties);
97
- server.tool(tool.name, zodSchema.shape, wrappedHandler);
98
- registeredCount++;
54
+ const wrappedHandler = async (args) => {
55
+ try {
56
+ // The handler functions are already typed with their specific parameter types
57
+ const result = await handler(args);
58
+ return {
59
+ content: result.toolResult.content.map((item) => ({
60
+ ...item,
61
+ type: "text"
62
+ })),
63
+ isError: result.toolResult.isError
64
+ };
65
+ }
66
+ catch (error) {
67
+ // Return error as tool result instead of throwing
68
+ return {
69
+ content: [{
70
+ type: "text",
71
+ text: `Error executing ${tool.name}: ${error.message || String(error)}`
72
+ }],
73
+ isError: true
74
+ };
75
+ }
76
+ };
77
+ const zodSchema = z.object(tool.inputSchema.properties);
78
+ server.tool(tool.name, zodSchema.shape, wrappedHandler);
79
+ registeredCount++;
80
+ }
81
+ console.error(`✅ Registered ${registeredCount} of ${loadedTools.length} tools`);
82
+ return registeredCount;
99
83
  }
100
- console.error(`✅ Registered ${registeredCount} of ${allTools.length} tools`);
101
84
  async function main() {
102
- const { logToFile } = await import('./wordpress.js');
85
+ const { logToFile, initWordPress } = await import('./wordpress.js');
103
86
  logToFile('Starting WordPress MCP server...');
104
87
  // Environment variables are passed by MCP client (Claude Desktop, Cursor, etc.)
105
88
  // Don't exit here - let initWordPress() handle the validation
@@ -108,9 +91,28 @@ async function main() {
108
91
  }
109
92
  try {
110
93
  logToFile('Initializing WordPress client...');
111
- const { initWordPress } = await import('./wordpress.js');
112
94
  await initWordPress();
113
95
  logToFile('WordPress client initialized successfully.');
96
+ // Load tools with graceful plugin detection
97
+ logToFile('Loading tools with plugin detection...');
98
+ const { getFilteredToolsAsync, getFilteredHandlersAsync } = await import('./tools/index.js');
99
+ const loadedTools = await getFilteredToolsAsync();
100
+ const loadedHandlers = await getFilteredHandlersAsync(loadedTools);
101
+ logToFile(`Loaded ${loadedTools.length} tools based on installed plugins`);
102
+ // Create MCP server instance with loaded tools
103
+ const server = new McpServer({
104
+ name: generateServerName(),
105
+ version: "1.0.7"
106
+ }, {
107
+ capabilities: {
108
+ tools: loadedTools.reduce((acc, tool) => {
109
+ acc[tool.name] = tool;
110
+ return acc;
111
+ }, {})
112
+ }
113
+ });
114
+ // Register tools with handlers
115
+ registerTools(server, loadedTools, loadedHandlers);
114
116
  logToFile('Setting up server transport...');
115
117
  const transport = new StdioServerTransport();
116
118
  await server.connect(transport);
@@ -1,3 +1,13 @@
1
1
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
2
+ /**
3
+ * Async version of getFilteredTools() that detects installed plugins
4
+ * and only loads tools for installed plugins when ENABLED_TOOLS='all' or not set
5
+ */
6
+ declare function getFilteredToolsAsync(): Promise<Tool[]>;
7
+ /**
8
+ * Async version that returns handlers matching the tools loaded by getFilteredToolsAsync()
9
+ */
10
+ declare function getFilteredHandlersAsync(loadedTools: Tool[]): Promise<Record<string, any>>;
2
11
  export declare const allTools: Tool[];
3
12
  export declare const toolHandlers: Record<string, any>;
13
+ export { getFilteredToolsAsync, getFilteredHandlersAsync };
@@ -1,3 +1,4 @@
1
+ import { detectInstalledPlugins } from '../wordpress.js';
1
2
  import { unifiedContentTools, unifiedContentHandlers } from './unified-content.js';
2
3
  import { unifiedTaxonomyTools, unifiedTaxonomyHandlers } from './unified-taxonomies.js';
3
4
  import { pluginTools, pluginHandlers } from './plugins.js';
@@ -129,46 +130,55 @@ const handlerCategories = {
129
130
  ...debugHandlers
130
131
  }
131
132
  };
133
+ // Plugin slug mappings - which plugin slugs indicate which tool categories should load
134
+ const pluginRequirements = {
135
+ fluentcommunity: ['fluent-community', 'fluentcommunity'],
136
+ 'fluentcommunity-core': ['fluent-community', 'fluentcommunity'],
137
+ 'fluentcommunity-learning': ['fluent-community', 'fluentcommunity'],
138
+ fluentcart: ['fluent-cart', 'fluentcart', 'wp-payment-form'],
139
+ fluentcrm: ['fluent-crm', 'fluentcrm'],
140
+ mlplugins: ['ml-image-editor', 'ml-media-hub', 'fluent-affiliate'],
141
+ pro: ['fluent-mcp-pro', 'fluentmcp-pro']
142
+ };
143
+ /**
144
+ * Check if any of the required plugin slugs are installed
145
+ */
146
+ function hasAnyPlugin(installedPlugins, requiredSlugs) {
147
+ return requiredSlugs.some(slug => installedPlugins.has(slug));
148
+ }
132
149
  // Filter tools based on ENABLED_TOOLS environment variable
133
150
  function getFilteredTools() {
134
151
  const enabledTools = process.env.ENABLED_TOOLS?.toLowerCase();
135
- if (!enabledTools || enabledTools === 'all') {
136
- // No filter or 'all' - load all tools
137
- return [
138
- ...toolCategories.wordpress,
139
- ...toolCategories.fluentcommunity,
140
- ...toolCategories.fluentcart,
141
- ...toolCategories.fluentcrm,
142
- ...toolCategories.mlplugins,
143
- ...toolCategories.pro,
144
- ...toolCategories.debug
145
- ];
146
- }
147
- // Map user-friendly names to internal category names
148
- const categoryMap = {
149
- 'wordpress': 'wordpress',
150
- 'fluent-community': 'fluentcommunity',
151
- 'fluentcommunity': 'fluentcommunity',
152
- 'fluentcommunity-core': 'fluentcommunity-core',
153
- 'fluent-community-core': 'fluentcommunity-core',
154
- 'fluentcommunity-learning': 'fluentcommunity-learning',
155
- 'fluent-community-learning': 'fluentcommunity-learning',
156
- 'fluent-cart': 'fluentcart',
157
- 'fluentcart': 'fluentcart',
158
- 'fluent-crm': 'fluentcrm',
159
- 'fluentcrm': 'fluentcrm',
160
- 'mlplugins': 'mlplugins',
161
- 'pro': 'pro',
162
- 'fluentmcp-pro': 'pro',
163
- 'fluent-mcp-pro': 'pro',
164
- 'debug': 'debug'
165
- };
166
- const category = categoryMap[enabledTools];
167
- if (category && toolCategories[category]) {
168
- console.error(`📦 Loading only: ${enabledTools} (${toolCategories[category].length} tools)`);
169
- return toolCategories[category];
152
+ // If specific category requested, honor it (no detection needed)
153
+ if (enabledTools && enabledTools !== 'all') {
154
+ // Map user-friendly names to internal category names
155
+ const categoryMap = {
156
+ 'wordpress': 'wordpress',
157
+ 'fluent-community': 'fluentcommunity',
158
+ 'fluentcommunity': 'fluentcommunity',
159
+ 'fluentcommunity-core': 'fluentcommunity-core',
160
+ 'fluent-community-core': 'fluentcommunity-core',
161
+ 'fluentcommunity-learning': 'fluentcommunity-learning',
162
+ 'fluent-community-learning': 'fluentcommunity-learning',
163
+ 'fluent-cart': 'fluentcart',
164
+ 'fluentcart': 'fluentcart',
165
+ 'fluent-crm': 'fluentcrm',
166
+ 'fluentcrm': 'fluentcrm',
167
+ 'mlplugins': 'mlplugins',
168
+ 'pro': 'pro',
169
+ 'fluentmcp-pro': 'pro',
170
+ 'fluent-mcp-pro': 'pro',
171
+ 'debug': 'debug'
172
+ };
173
+ const category = categoryMap[enabledTools];
174
+ if (category && toolCategories[category]) {
175
+ console.error(`📦 Loading only: ${enabledTools} (${toolCategories[category].length} tools)`);
176
+ return toolCategories[category];
177
+ }
178
+ console.error(`⚠️ Unknown ENABLED_TOOLS value: ${enabledTools}. Loading all tools.`);
170
179
  }
171
- console.error(`⚠️ Unknown ENABLED_TOOLS value: ${enabledTools}. Loading all tools.`);
180
+ // ENABLED_TOOLS not set or 'all' - load all tools without detection
181
+ // Detection will happen async in getFilteredToolsAsync()
172
182
  return [
173
183
  ...toolCategories.wordpress,
174
184
  ...toolCategories.fluentcommunity,
@@ -179,6 +189,51 @@ function getFilteredTools() {
179
189
  ...toolCategories.debug
180
190
  ];
181
191
  }
192
+ /**
193
+ * Async version of getFilteredTools() that detects installed plugins
194
+ * and only loads tools for installed plugins when ENABLED_TOOLS='all' or not set
195
+ */
196
+ async function getFilteredToolsAsync() {
197
+ const enabledTools = process.env.ENABLED_TOOLS?.toLowerCase();
198
+ // If specific category requested, use sync version (no detection needed)
199
+ if (enabledTools && enabledTools !== 'all') {
200
+ return getFilteredTools();
201
+ }
202
+ // Detect installed plugins
203
+ console.error('🔍 Detecting installed plugins...');
204
+ const installedPlugins = await detectInstalledPlugins();
205
+ if (installedPlugins.size === 0) {
206
+ // Detection failed - fall back to loading all tools (safe default)
207
+ console.error('⚠️ Plugin detection failed. Loading all tools as fallback.');
208
+ return getFilteredTools();
209
+ }
210
+ // Always include WordPress core tools and debug tools
211
+ const tools = [...toolCategories.wordpress, ...toolCategories.debug];
212
+ let loadedCategories = ['wordpress', 'debug'];
213
+ // Conditionally add plugin tools based on detection
214
+ if (hasAnyPlugin(installedPlugins, pluginRequirements.fluentcommunity)) {
215
+ tools.push(...toolCategories.fluentcommunity);
216
+ loadedCategories.push('fluentcommunity');
217
+ }
218
+ if (hasAnyPlugin(installedPlugins, pluginRequirements.fluentcart)) {
219
+ tools.push(...toolCategories.fluentcart);
220
+ loadedCategories.push('fluentcart');
221
+ }
222
+ if (hasAnyPlugin(installedPlugins, pluginRequirements.fluentcrm)) {
223
+ tools.push(...toolCategories.fluentcrm);
224
+ loadedCategories.push('fluentcrm');
225
+ }
226
+ if (hasAnyPlugin(installedPlugins, pluginRequirements.mlplugins)) {
227
+ tools.push(...toolCategories.mlplugins);
228
+ loadedCategories.push('mlplugins');
229
+ }
230
+ if (hasAnyPlugin(installedPlugins, pluginRequirements.pro)) {
231
+ tools.push(...toolCategories.pro);
232
+ loadedCategories.push('pro');
233
+ }
234
+ console.error(`✅ Loaded ${tools.length} tools from categories: ${loadedCategories.join(', ')}`);
235
+ return tools;
236
+ }
182
237
  function getFilteredHandlers() {
183
238
  const enabledTools = process.env.ENABLED_TOOLS?.toLowerCase();
184
239
  if (!enabledTools || enabledTools === 'all') {
@@ -226,6 +281,45 @@ function getFilteredHandlers() {
226
281
  ...handlerCategories.debug
227
282
  };
228
283
  }
229
- // Export filtered tools and handlers
284
+ /**
285
+ * Async version that returns handlers matching the tools loaded by getFilteredToolsAsync()
286
+ */
287
+ async function getFilteredHandlersAsync(loadedTools) {
288
+ const enabledTools = process.env.ENABLED_TOOLS?.toLowerCase();
289
+ // If specific category requested, use sync version
290
+ if (enabledTools && enabledTools !== 'all') {
291
+ return getFilteredHandlers();
292
+ }
293
+ // Build handlers object based on which tools were loaded
294
+ const handlers = {
295
+ ...handlerCategories.wordpress,
296
+ ...handlerCategories.debug
297
+ };
298
+ // Check which tool categories are present in loadedTools
299
+ const hasFluentCommunity = loadedTools.some(t => t.name.startsWith('fc_'));
300
+ const hasFluentCart = loadedTools.some(t => t.name.startsWith('fcart_'));
301
+ const hasFluentCRM = loadedTools.some(t => t.name.startsWith('fcrm_'));
302
+ const hasMLPlugins = loadedTools.some(t => t.name.startsWith('mlimg_') || t.name.startsWith('mlmh_') || t.name.startsWith('faf_'));
303
+ const hasPro = loadedTools.some(t => t.name.startsWith('fmcp_pro_'));
304
+ if (hasFluentCommunity) {
305
+ Object.assign(handlers, handlerCategories.fluentcommunity);
306
+ }
307
+ if (hasFluentCart) {
308
+ Object.assign(handlers, handlerCategories.fluentcart);
309
+ }
310
+ if (hasFluentCRM) {
311
+ Object.assign(handlers, handlerCategories.fluentcrm);
312
+ }
313
+ if (hasMLPlugins) {
314
+ Object.assign(handlers, handlerCategories.mlplugins);
315
+ }
316
+ if (hasPro) {
317
+ Object.assign(handlers, handlerCategories.pro);
318
+ }
319
+ return handlers;
320
+ }
321
+ // Export filtered tools and handlers (sync versions for backward compatibility)
230
322
  export const allTools = getFilteredTools();
231
323
  export const toolHandlers = getFilteredHandlers();
324
+ // Export async versions for graceful plugin detection
325
+ export { getFilteredToolsAsync, getFilteredHandlersAsync };
@@ -16,6 +16,11 @@ export declare function makeWordPressRequest(method: string, endpoint: string, d
16
16
  isFormData?: boolean;
17
17
  rawResponse?: boolean;
18
18
  }): Promise<any>;
19
+ /**
20
+ * Detect which plugins are installed and active
21
+ * Returns a set of plugin slugs that are active
22
+ */
23
+ export declare function detectInstalledPlugins(): Promise<Set<string>>;
19
24
  /**
20
25
  * Make a request to the WordPress.org Plugin Repository API
21
26
  * @param searchQuery Search query string
@@ -145,6 +145,32 @@ Data: ${JSON.stringify(error.response?.data || {}, null, 2)}
145
145
  throw error;
146
146
  }
147
147
  }
148
+ /**
149
+ * Detect which plugins are installed and active
150
+ * Returns a set of plugin slugs that are active
151
+ */
152
+ export async function detectInstalledPlugins() {
153
+ const installedPlugins = new Set();
154
+ try {
155
+ // Get list of active plugins
156
+ const response = await makeWordPressRequest('GET', 'wp/v2/plugins', { status: 'active' });
157
+ if (Array.isArray(response)) {
158
+ response.forEach((plugin) => {
159
+ // Extract plugin slug from plugin path (e.g., "fluent-crm/fluent-crm.php" -> "fluent-crm")
160
+ const slug = plugin.plugin?.split('/')[0] || plugin.slug;
161
+ if (slug) {
162
+ installedPlugins.add(slug);
163
+ }
164
+ });
165
+ }
166
+ logToFile(`Detected active plugins: ${Array.from(installedPlugins).join(', ')}`);
167
+ }
168
+ catch (error) {
169
+ // Silently fail - if we can't detect plugins, we'll load all tools
170
+ logToFile(`Plugin detection failed (will load all tools): ${error.message}`);
171
+ }
172
+ return installedPlugins;
173
+ }
148
174
  /**
149
175
  * Make a request to the WordPress.org Plugin Repository API
150
176
  * @param searchQuery Search query string
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wplaunchify/ml-mcp-server",
3
- "version": "2.5.5",
3
+ "version": "2.5.7",
4
4
  "description": "Universal MCP Server for WordPress + Fluent Suite (Community, CRM, Cart) + FluentMCP Pro. Comprehensive tools for AI-powered WordPress management via Claude, Cursor, and other MCP clients.",
5
5
  "type": "module",
6
6
  "main": "./build/server.js",