mage-remote-run 0.12.0 → 0.14.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.
@@ -9,6 +9,10 @@ export function registerStoresCommands(program) {
9
9
  // Store Groups
10
10
  const groups = stores.command('group').description('Manage store groups');
11
11
 
12
+
13
+ //-------------------------------------------------------
14
+ // "store group list" Command
15
+ //-------------------------------------------------------
12
16
  groups.command('list')
13
17
  .description('List store groups')
14
18
  .option('-f, --format <type>', 'Output format (text, json, xml)', 'text')
@@ -40,6 +44,10 @@ Examples:
40
44
  } catch (e) { handleError(e); }
41
45
  });
42
46
 
47
+
48
+ //-------------------------------------------------------
49
+ // "store group search" Command
50
+ //-------------------------------------------------------
43
51
  groups.command('search <query>')
44
52
  .description('Search store groups')
45
53
  .addHelpText('after', `
@@ -56,6 +64,10 @@ Examples:
56
64
  } catch (e) { handleError(e); }
57
65
  });
58
66
 
67
+
68
+ //-------------------------------------------------------
69
+ // "store group delete" Command
70
+ //-------------------------------------------------------
59
71
  groups.command('delete <id>')
60
72
  .description('Delete store group')
61
73
  .addHelpText('after', `
@@ -72,6 +84,10 @@ Examples:
72
84
  } catch (e) { handleError(e); }
73
85
  });
74
86
 
87
+
88
+ //-------------------------------------------------------
89
+ // "store group edit" Command
90
+ //-------------------------------------------------------
75
91
  groups.command('edit <id>')
76
92
  .description('Edit store group')
77
93
  .addHelpText('after', `
@@ -99,6 +115,10 @@ Examples:
99
115
  // Store Views
100
116
  const views = stores.command('view').description('Manage store views');
101
117
 
118
+
119
+ //-------------------------------------------------------
120
+ // "store view list" Command
121
+ //-------------------------------------------------------
102
122
  views.command('list')
103
123
  .description('List store views')
104
124
  .option('-f, --format <type>', 'Output format (text, json, xml)', 'text')
@@ -130,6 +150,10 @@ Examples:
130
150
  } catch (e) { handleError(e); }
131
151
  });
132
152
 
153
+
154
+ //-------------------------------------------------------
155
+ // "store view search" Command
156
+ //-------------------------------------------------------
133
157
  views.command('search <query>')
134
158
  .description('Search store views')
135
159
  .addHelpText('after', `
@@ -146,6 +170,10 @@ Examples:
146
170
  } catch (e) { handleError(e); }
147
171
  });
148
172
 
173
+
174
+ //-------------------------------------------------------
175
+ // "store view delete" Command
176
+ //-------------------------------------------------------
149
177
  views.command('delete <id>')
150
178
  .description('Delete store view')
151
179
  .addHelpText('after', `
@@ -162,6 +190,10 @@ Examples:
162
190
  } catch (e) { handleError(e); }
163
191
  });
164
192
 
193
+
194
+ //-------------------------------------------------------
195
+ // "store view edit" Command
196
+ //-------------------------------------------------------
165
197
  views.command('edit <id>')
166
198
  .description('Edit store view')
167
199
  .addHelpText('after', `
@@ -189,6 +221,10 @@ Examples:
189
221
  // Store Configs
190
222
  const configs = stores.command('config').description('Manage store configurations');
191
223
 
224
+
225
+ //-------------------------------------------------------
226
+ // "store config list" Command
227
+ //-------------------------------------------------------
192
228
  configs.command('list')
193
229
  .description('List store configurations')
194
230
  .addHelpText('after', `
@@ -7,6 +7,10 @@ export function registerTaxCommands(program) {
7
7
 
8
8
  const taxClass = tax.command('class').description('Manage tax classes');
9
9
 
10
+
11
+ //-------------------------------------------------------
12
+ // "tax class list" Command
13
+ //-------------------------------------------------------
10
14
  taxClass.command('list')
11
15
  .description('List tax classes')
12
16
  .option('-p, --page <number>', 'Page number', '1')
@@ -49,6 +53,10 @@ Examples:
49
53
  } catch (e) { handleError(e); }
50
54
  });
51
55
 
56
+
57
+ //-------------------------------------------------------
58
+ // "tax class show" Command
59
+ //-------------------------------------------------------
52
60
  taxClass.command('show <id>')
53
61
  .description('Show tax class details')
54
62
  .addHelpText('after', `
@@ -5,6 +5,10 @@ import chalk from 'chalk';
5
5
  export function registerWebhooksCommands(program) {
6
6
  const webhooks = program.command('webhook').description('Manage webhooks');
7
7
 
8
+
9
+ //-------------------------------------------------------
10
+ // "webhook list" Command
11
+ //-------------------------------------------------------
8
12
  webhooks.command('list')
9
13
  .description('List available webhooks')
10
14
  .option('-p, --page <number>', 'Page number', '1')
@@ -6,6 +6,10 @@ import inquirer from 'inquirer';
6
6
  export function registerWebsitesCommands(program) {
7
7
  const websites = program.command('website').description('Manage websites');
8
8
 
9
+
10
+ //-------------------------------------------------------
11
+ // "website list" Command
12
+ //-------------------------------------------------------
9
13
  websites.command('list')
10
14
  .description('List all websites')
11
15
  .option('-f, --format <type>', 'Output format (text, json, xml)', 'text')
@@ -37,6 +41,10 @@ Examples:
37
41
  } catch (e) { handleError(e); }
38
42
  });
39
43
 
44
+
45
+ //-------------------------------------------------------
46
+ // "website search" Command
47
+ //-------------------------------------------------------
40
48
  websites.command('search <query>')
41
49
  .description('Search websites by code or name (local filter)')
42
50
  .addHelpText('after', `
@@ -55,6 +63,10 @@ Examples:
55
63
  } catch (e) { handleError(e); }
56
64
  });
57
65
 
66
+
67
+ //-------------------------------------------------------
68
+ // "website delete" Command
69
+ //-------------------------------------------------------
58
70
  websites.command('delete <id>')
59
71
  .description('Delete a website by ID')
60
72
  .addHelpText('after', `
@@ -77,6 +89,10 @@ Examples:
77
89
  } catch (e) { handleError(e); }
78
90
  });
79
91
 
92
+
93
+ //-------------------------------------------------------
94
+ // "website edit" Command
95
+ //-------------------------------------------------------
80
96
  websites.command('edit <id>')
81
97
  .description('Edit a website')
82
98
  .addHelpText('after', `
package/lib/mcp.js ADDED
@@ -0,0 +1,263 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
+ import { z } from "zod";
5
+ import http from "http";
6
+ import chalk from "chalk";
7
+ import { Command } from "commander";
8
+
9
+ // Import command registry
10
+ import { registerAllCommands } from './command-registry.js';
11
+
12
+ // Helper to strip ANSI codes for cleaner output
13
+ function stripAnsi(str) {
14
+ return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
15
+ }
16
+
17
+ /**
18
+ * Starts the MCP server.
19
+ * @param {Object} options Configuration options
20
+ * @param {string} options.transport 'stdio' or 'http'
21
+ * @param {string} [options.host] Host for HTTP server
22
+ * @param {number} [options.port] Port for HTTP server
23
+ */
24
+ export async function startMcpServer(options) {
25
+ // 1. Setup a dynamic program to discovery commands
26
+ const program = setupProgram();
27
+
28
+ const server = new McpServer({
29
+ name: "mage-remote-run",
30
+ version: "1.0.0"
31
+ });
32
+
33
+ const toolsCount = registerTools(server, program);
34
+
35
+ if (options.transport === 'http') {
36
+ const host = options.host || '127.0.0.1';
37
+ const port = options.port || 18098;
38
+
39
+ const transport = new StreamableHTTPServerTransport();
40
+
41
+ const httpServer = http.createServer(async (req, res) => {
42
+ if (req.url === '/sse' || (req.url === '/messages' && req.method === 'POST')) {
43
+ await transport.handleRequest(req, res);
44
+ } else {
45
+ res.writeHead(404);
46
+ res.end();
47
+ }
48
+ });
49
+
50
+ httpServer.listen(port, host, () => {
51
+ console.error(`MCP Server running on http://${host}:${port}`);
52
+ console.error(`Protocol: HTTP (SSE)`);
53
+ console.error(`Registered Tools: ${toolsCount}`);
54
+ });
55
+
56
+ await server.connect(transport);
57
+
58
+ } else {
59
+ // STDIO
60
+ console.error(`Protocol: stdio`);
61
+ console.error(`Registered Tools: ${toolsCount}`);
62
+ const transport = new StdioServerTransport();
63
+ await server.connect(transport);
64
+ }
65
+ }
66
+
67
+ function registerTools(server, program) {
68
+ let count = 0;
69
+
70
+ function processCommand(cmd, parentName = '') {
71
+ const cmdName = parentName ? `${parentName}_${cmd.name()}` : cmd.name();
72
+
73
+ // If it has subcommands, process them
74
+ if (cmd.commands && cmd.commands.length > 0) {
75
+ cmd.commands.forEach(subCmd => processCommand(subCmd, cmdName));
76
+ return;
77
+ }
78
+
79
+ // It's a leaf command, register as tool
80
+ // Tool name: Replace spaces/colons with underscores.
81
+ // Example: website list -> website_list
82
+ const toolName = cmdName.replace(/[^a-zA-Z0-9_]/g, '_');
83
+
84
+ const schema = {};
85
+
86
+ // Arguments
87
+ // Commander args: cmd._args
88
+ // Options: cmd.options
89
+
90
+ const zodShape = {};
91
+
92
+ cmd._args.forEach(arg => {
93
+ // arg.name(), arg.required
94
+ if (arg.required) {
95
+ zodShape[arg.name()] = z.string().describe(arg.description || '');
96
+ } else {
97
+ zodShape[arg.name()] = z.string().optional().describe(arg.description || '');
98
+ }
99
+ });
100
+
101
+ cmd.options.forEach(opt => {
102
+ const name = opt.name(); // e.g. "format" for --format
103
+ // Check flags to guess type.
104
+ // -f, --format <type> -> string
105
+ // -v, --verbose -> boolean
106
+
107
+ if (opt.flags.includes('<')) {
108
+ // Takes an argument, assume string
109
+ zodShape[name] = z.string().optional().describe(opt.description);
110
+ } else {
111
+ // Boolean flag
112
+ zodShape[name] = z.boolean().optional().describe(opt.description);
113
+ }
114
+ });
115
+
116
+ server.tool(
117
+ toolName,
118
+ zodShape,
119
+ async (args) => {
120
+ return await executeCommand(cmd, args, parentName);
121
+ }
122
+ );
123
+ count++;
124
+ }
125
+
126
+
127
+ program.commands.forEach(cmd => processCommand(cmd));
128
+ return count;
129
+ }
130
+
131
+ // Re-register all commands on a fresh program instance
132
+ // We export this logic so we can reuse it
133
+ function setupProgram() {
134
+ const program = new Command();
135
+
136
+ // Silence output for the main program instance to avoid double printing during parsing
137
+ program.configureOutput({
138
+ writeOut: (str) => { },
139
+ writeErr: (str) => { }
140
+ });
141
+
142
+ registerAllCommands(program);
143
+
144
+ return program;
145
+ }
146
+
147
+ async function executeCommand(cmdDefinition, args, parentName) {
148
+ // cmdDefinition is the original command object from discovery, used for context if needed,
149
+ // but here we mainly need the path to find it in the new program.
150
+ // Actually, we can just reconstruct the path from parentName + cmd.name()
151
+
152
+ // Intercept Console
153
+ let output = '';
154
+ const originalLog = console.log;
155
+ const originalError = console.error;
156
+
157
+ // Simple custom logger
158
+ const logInterceptor = (...msgArgs) => {
159
+ const line = msgArgs.map(String).join(' ');
160
+ output += stripAnsi(line) + '\n';
161
+ };
162
+
163
+ console.log = logInterceptor;
164
+ console.error = logInterceptor;
165
+
166
+ try {
167
+ const program = setupProgram();
168
+
169
+ // Construct argv
170
+ // We need to build [node, script, command, subcommand, ..., args, options]
171
+ const argv = ['node', 'mage-remote-run'];
172
+
173
+ // Reconstruct command path
174
+ if (parentName) {
175
+ // parentName might be "website" or "website_domain" (if nested deeper? current logic supports 1 level nesting)
176
+ // Current processCommand logic: `cmdName = parentName ? ${parentName}_${cmd.name()} : cmd.name()`
177
+ // But parentName passed to executeCommand is the prefix.
178
+ // Wait, registerTools calls: `executeCommand(cmd, args, parentName)`
179
+ // If parentName is "website", and cmd is "list", we need "website list"
180
+
181
+ // NOTE: parentName in processCommand is built recursively with underscores?
182
+ // In processCommand(subCmd, cmdName), cmdName is "parent_sub".
183
+ // So if we have website -> list.
184
+ // processCommand(website) -> processCommand(list, "website")
185
+ // parentName in executeCommand is "website".
186
+ // cmd.name() is "list".
187
+
188
+ // However, parentName might contain underscores if deeper nesting?
189
+ // "store_config_list" -> parent "store_config", cmd "list".
190
+ // Commander commands are usually space separated in argv.
191
+
192
+ // We need to parse parentName back to argv tokens?
193
+ // Or better: store the "command path" as an array in the tool context.
194
+
195
+ // Let's rely on standard splitting by underscore, assuming command names don't have underscores.
196
+ // Or we can assume parentName matches the command structure.
197
+
198
+ // Safest: splitting parentName by UNDERSCORE might be risky if command names have underscores.
199
+ // But standard commands here: website, store, etc. don't.
200
+
201
+ // Actually, we can just push parentName, then cmd.name()
202
+ // But parentName comes from `cmdName` variable passed as `parentName` to recursive call.
203
+ // `processCommand(subCmd, cmdName)`
204
+ // `cmdName` = `parentName_cmd.name()`.
205
+ // So for `website list`:
206
+ // `processCommand(website, '')` -> `cmdName="website"`.
207
+ // -> `processCommand(list, "website")`.
208
+ // -> register tool "website_list". `parentName` passed to execute is "website".
209
+
210
+ // So `parentName` is the accumulated prefix with underscores.
211
+
212
+ const parts = parentName.split('_');
213
+ argv.push(...parts);
214
+ }
215
+
216
+ argv.push(cmdDefinition.name());
217
+
218
+ // Add arguments and options from the tool args
219
+
220
+ // 1. Positional arguments
221
+ cmdDefinition._args.forEach(arg => {
222
+ if (args[arg.name()]) {
223
+ argv.push(args[arg.name()]);
224
+ }
225
+ });
226
+
227
+ // 2. Options
228
+ cmdDefinition.options.forEach(opt => {
229
+ const name = opt.name(); // e.g. "format"
230
+ if (args[name] !== undefined) {
231
+ const val = args[name];
232
+ if (opt.flags.includes('<')) {
233
+ // String option
234
+ argv.push(`--${name}`);
235
+ argv.push(val);
236
+ } else {
237
+ // Boolean flag
238
+ if (val === true) {
239
+ argv.push(`--${name}`);
240
+ }
241
+ }
242
+ }
243
+ });
244
+
245
+ // Execute
246
+ await program.parseAsync(argv);
247
+
248
+ return {
249
+ content: [{ type: "text", text: output }]
250
+ };
251
+
252
+ } catch (e) {
253
+ // Commander throws nicely formatted errors usually, but we suppressed output.
254
+ // If it throws, it might be a cleaner exit error.
255
+ return {
256
+ content: [{ type: "text", text: output + `\nError: ${e.message}` }],
257
+ isError: true
258
+ };
259
+ } finally {
260
+ console.log = originalLog;
261
+ console.error = originalError;
262
+ }
263
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mage-remote-run",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "The remote swiss army knife for Magento Open Source, Mage-OS, Adobe Commerce",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -31,6 +31,7 @@
31
31
  ],
32
32
  "dependencies": {
33
33
  "@inquirer/prompts": "^8.1.0",
34
+ "@modelcontextprotocol/sdk": "^1.25.1",
34
35
  "axios": "^1.13.2",
35
36
  "chalk": "^5.6.2",
36
37
  "cli-table3": "^0.6.5",
@@ -40,7 +41,8 @@
40
41
  "inquirer": "^13.1.0",
41
42
  "mkdirp": "^3.0.1",
42
43
  "oauth-1.0a": "^2.2.6",
43
- "openapi-client-axios": "^7.8.0"
44
+ "openapi-client-axios": "^7.8.0",
45
+ "zod": "^4.2.1"
44
46
  },
45
47
  "devDependencies": {
46
48
  "jest": "^30.2.0"