@deriv-com/fe-mcp-servers 0.0.9 → 0.0.10

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/README.md CHANGED
@@ -31,14 +31,43 @@ mcps/
31
31
  npm install -g @deriv-com/fe-mcp-servers
32
32
  ```
33
33
 
34
- ### Get Your Configuration Path
35
- Copy and run this command to get the exact path for your MCP configuration:
34
+ ### CLI Commands
35
+
36
+ After installation, use the `fe-mcp` CLI to manage MCP servers:
37
+
38
+ ```bash
39
+ # List all available MCP servers
40
+ fe-mcp list
41
+
42
+ # Interactive config generator - creates file & opens it for copy-paste
43
+ fe-mcp code
44
+
45
+ # Show detailed info about a specific server
46
+ fe-mcp info shift-ai
47
+
48
+ # Output MCP client configuration JSON
49
+ fe-mcp config shift-ai
50
+
51
+ # Show help
52
+ fe-mcp help
53
+
54
+ # Show version
55
+ fe-mcp --version
56
+ ```
57
+
58
+ ### Quick Setup (Recommended)
59
+
60
+ The easiest way to get your MCP config:
36
61
 
37
62
  ```bash
38
- echo "$(npm root -g)/@deriv-com/fe-mcp-servers/dist/SERVER_NAME/mcp-server.js"
63
+ fe-mcp code
39
64
  ```
40
65
 
41
- Replace `SERVER_NAME` with the specific server you want (e.g., `shift-ai`).
66
+ This will:
67
+ 1. Show you a list of available MCP servers
68
+ 2. Ask you to select one
69
+ 3. Generate the configuration JSON with the correct path
70
+ 4. Save it to a file and auto-open it for easy copy-paste
42
71
 
43
72
  ### MCP Configuration Template
44
73
  ```json
@@ -46,7 +75,7 @@ Replace `SERVER_NAME` with the specific server you want (e.g., `shift-ai`).
46
75
  "mcpServers": {
47
76
  "server-name": {
48
77
  "command": "node",
49
- "args": ["<PASTE_PATH_FROM_ABOVE>"]
78
+ "args": ["<PATH_FROM_FE-MCP>"]
50
79
  }
51
80
  }
52
81
  }
package/bin/fe-mcp.js ADDED
@@ -0,0 +1,424 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { execSync, spawn } from 'child_process';
7
+ import readline from 'readline';
8
+ import os from 'os';
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ const packageRoot = path.join(__dirname, '..');
12
+ const distDir = path.join(packageRoot, 'dist');
13
+ const srcDir = packageRoot;
14
+
15
+ // Read package.json for version
16
+ const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf-8'));
17
+
18
+ // ANSI colors
19
+ const colors = {
20
+ reset: '\x1b[0m',
21
+ bright: '\x1b[1m',
22
+ dim: '\x1b[2m',
23
+ cyan: '\x1b[36m',
24
+ green: '\x1b[32m',
25
+ yellow: '\x1b[33m',
26
+ blue: '\x1b[34m',
27
+ magenta: '\x1b[35m',
28
+ red: '\x1b[31m',
29
+ };
30
+
31
+ const c = (color, text) => `${colors[color]}${text}${colors.reset}`;
32
+
33
+ /**
34
+ * Get all available MCP servers
35
+ */
36
+ function getMcpServers() {
37
+ const servers = [];
38
+
39
+ // Check dist directory for built servers
40
+ if (fs.existsSync(distDir)) {
41
+ const entries = fs.readdirSync(distDir, { withFileTypes: true });
42
+ for (const entry of entries) {
43
+ if (entry.isDirectory()) {
44
+ const mcpServerPath = path.join(distDir, entry.name, 'mcp-server.js');
45
+ const readmePath = path.join(distDir, entry.name, 'README.md');
46
+
47
+ if (fs.existsSync(mcpServerPath)) {
48
+ const server = {
49
+ name: entry.name,
50
+ path: mcpServerPath,
51
+ hasReadme: fs.existsSync(readmePath),
52
+ readmePath: readmePath,
53
+ built: true,
54
+ };
55
+
56
+ // Try to extract description from README
57
+ if (server.hasReadme) {
58
+ const readme = fs.readFileSync(readmePath, 'utf-8');
59
+ const descMatch = readme.match(/^#[^\n]+\n+([^\n#]+)/);
60
+ if (descMatch) {
61
+ server.description = descMatch[1].trim();
62
+ }
63
+ }
64
+
65
+ servers.push(server);
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ // Also check source directories for unbuilt servers
72
+ const srcEntries = fs.readdirSync(srcDir, { withFileTypes: true });
73
+ for (const entry of srcEntries) {
74
+ if (entry.isDirectory() &&
75
+ !['dist', 'bin', 'node_modules', 'scripts'].includes(entry.name) &&
76
+ !entry.name.startsWith('.')) {
77
+ const srcMcpPath = path.join(srcDir, entry.name, 'src', 'mcp-server.js');
78
+
79
+ if (fs.existsSync(srcMcpPath)) {
80
+ // Check if already in servers list (built version)
81
+ const existing = servers.find(s => s.name === entry.name);
82
+ if (!existing) {
83
+ const readmePath = path.join(srcDir, entry.name, 'README.md');
84
+ const server = {
85
+ name: entry.name,
86
+ path: srcMcpPath,
87
+ hasReadme: fs.existsSync(readmePath),
88
+ readmePath: readmePath,
89
+ built: false,
90
+ };
91
+
92
+ if (server.hasReadme) {
93
+ const readme = fs.readFileSync(readmePath, 'utf-8');
94
+ const descMatch = readme.match(/^#[^\n]+\n+([^\n#]+)/);
95
+ if (descMatch) {
96
+ server.description = descMatch[1].trim();
97
+ }
98
+ }
99
+
100
+ servers.push(server);
101
+ }
102
+ }
103
+ }
104
+ }
105
+
106
+ return servers;
107
+ }
108
+
109
+ /**
110
+ * List all available MCP servers
111
+ */
112
+ function listServers() {
113
+ const servers = getMcpServers();
114
+
115
+ if (servers.length === 0) {
116
+ console.log(c('yellow', '\n⚠️ No MCP servers found.'));
117
+ console.log(c('dim', ' Run "npm run build" to build the servers first.\n'));
118
+ return;
119
+ }
120
+
121
+ console.log(c('bright', '\n📦 Available MCP Servers\n'));
122
+ console.log(c('dim', '─'.repeat(60)));
123
+
124
+ for (const server of servers) {
125
+ const status = server.built
126
+ ? c('green', '● built')
127
+ : c('yellow', '○ source only');
128
+
129
+ console.log(`\n ${c('cyan', server.name)} ${c('dim', `(${status})`)}`);
130
+
131
+ if (server.description) {
132
+ console.log(` ${c('dim', server.description)}`);
133
+ }
134
+ }
135
+
136
+ console.log(c('dim', '\n─'.repeat(60)));
137
+ console.log(c('dim', `\nTotal: ${servers.length} server(s)`));
138
+ console.log(c('dim', `\nUse "${c('cyan', 'fe-mcp info <name>')}" for details`));
139
+ console.log(c('dim', `Use "${c('cyan', 'fe-mcp path <name>')}" to get the executable path\n`));
140
+ }
141
+
142
+ /**
143
+ * Show detailed info about a specific MCP server
144
+ */
145
+ function showInfo(serverName) {
146
+ const servers = getMcpServers();
147
+ const server = servers.find(s => s.name === serverName);
148
+
149
+ if (!server) {
150
+ console.log(c('red', `\n❌ MCP server "${serverName}" not found.\n`));
151
+ console.log(c('dim', 'Available servers:'));
152
+ servers.forEach(s => console.log(c('dim', ` - ${s.name}`)));
153
+ console.log();
154
+ process.exit(1);
155
+ }
156
+
157
+ console.log(c('bright', `\n📦 ${server.name}\n`));
158
+ console.log(c('dim', '─'.repeat(60)));
159
+
160
+ if (server.description) {
161
+ console.log(`\n${c('bright', 'Description:')}`);
162
+ console.log(` ${server.description}`);
163
+ }
164
+
165
+ console.log(`\n${c('bright', 'Status:')} ${server.built ? c('green', 'Built ✓') : c('yellow', 'Not built')}`);
166
+ console.log(`${c('bright', 'Path:')} ${server.path}`);
167
+
168
+ if (server.built) {
169
+ console.log(`\n${c('bright', 'MCP Configuration:')}`);
170
+ console.log(c('dim', ' Add this to your MCP client config:\n'));
171
+
172
+ const config = {
173
+ [server.name]: {
174
+ command: 'node',
175
+ args: [server.path]
176
+ }
177
+ };
178
+
179
+ console.log(c('cyan', JSON.stringify({ mcpServers: config }, null, 2)));
180
+ }
181
+
182
+ if (server.hasReadme) {
183
+ console.log(`\n${c('bright', 'Documentation:')} ${server.readmePath}`);
184
+ }
185
+
186
+ console.log(c('dim', '\n─'.repeat(60) + '\n'));
187
+ }
188
+
189
+ /**
190
+ * Show help
191
+ */
192
+ function showHelp() {
193
+ console.log(`
194
+ ${c('bright', '@deriv-com/fe-mcp-servers CLI')}
195
+
196
+ ${c('bright', 'Usage:')}
197
+ fe-mcp <command> [options]
198
+
199
+ ${c('bright', 'Commands:')}
200
+ ${c('cyan', 'list')} List all available MCP servers
201
+ ${c('cyan', 'code')} Interactive config generator (creates file & opens it)
202
+ ${c('cyan', 'info <name>')} Show detailed info about a specific server
203
+ ${c('cyan', 'config <name>')} Output MCP client configuration JSON
204
+ ${c('cyan', 'help')} Show this help message
205
+ ${c('cyan', '-v, --version')} Show version number
206
+
207
+ ${c('bright', 'Examples:')}
208
+ fe-mcp list
209
+ fe-mcp code
210
+ fe-mcp info shift-ai
211
+ fe-mcp config shift-ai
212
+
213
+ ${c('bright', 'Global Installation:')}
214
+ npm install -g @deriv-com/fe-mcp-servers
215
+ fe-mcp list
216
+ `);
217
+ }
218
+
219
+ /**
220
+ * Output MCP config JSON for a server
221
+ */
222
+ function outputConfig(serverName) {
223
+ const servers = getMcpServers();
224
+ const server = servers.find(s => s.name === serverName);
225
+
226
+ if (!server) {
227
+ console.error(c('red', `MCP server "${serverName}" not found.`));
228
+ process.exit(1);
229
+ }
230
+
231
+ if (!server.built) {
232
+ console.error(c('red', `MCP server "${serverName}" is not built yet.`));
233
+ console.error(c('dim', 'Run "npm run build" first.'));
234
+ process.exit(1);
235
+ }
236
+
237
+ const config = {
238
+ [server.name]: {
239
+ command: 'node',
240
+ args: [server.path]
241
+ }
242
+ };
243
+
244
+ console.log(JSON.stringify(config, null, 2));
245
+ }
246
+
247
+ /**
248
+ * Get global npm root path
249
+ */
250
+ function getGlobalNpmRoot() {
251
+ try {
252
+ return execSync('npm root -g', { encoding: 'utf-8' }).trim();
253
+ } catch (error) {
254
+ return null;
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Open a file with the default application
260
+ */
261
+ function openFile(filePath) {
262
+ const platform = os.platform();
263
+ let command;
264
+
265
+ if (platform === 'darwin') {
266
+ command = 'open';
267
+ } else if (platform === 'win32') {
268
+ command = 'start';
269
+ } else {
270
+ command = 'xdg-open';
271
+ }
272
+
273
+ try {
274
+ spawn(command, [filePath], { detached: true, stdio: 'ignore' }).unref();
275
+ } catch (error) {
276
+ console.log(c('yellow', `\n⚠️ Could not auto-open file. Please open manually:`));
277
+ console.log(c('cyan', ` ${filePath}\n`));
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Prompt user to select from a list
283
+ */
284
+ function promptSelect(question, options) {
285
+ return new Promise((resolve) => {
286
+ const rl = readline.createInterface({
287
+ input: process.stdin,
288
+ output: process.stdout
289
+ });
290
+
291
+ console.log(c('bright', `\n${question}\n`));
292
+
293
+ options.forEach((opt, i) => {
294
+ console.log(` ${c('cyan', `[${i + 1}]`)} ${opt.name}${opt.description ? c('dim', ` - ${opt.description.substring(0, 50)}...`) : ''}`);
295
+ });
296
+
297
+ console.log();
298
+
299
+ rl.question(c('bright', 'Enter number: '), (answer) => {
300
+ rl.close();
301
+ const index = parseInt(answer, 10) - 1;
302
+ if (index >= 0 && index < options.length) {
303
+ resolve(options[index]);
304
+ } else {
305
+ console.log(c('red', '\n❌ Invalid selection.\n'));
306
+ process.exit(1);
307
+ }
308
+ });
309
+ });
310
+ }
311
+
312
+ /**
313
+ * Generate MCP config file and open it
314
+ */
315
+ async function generateConfigFile() {
316
+ const servers = getMcpServers().filter(s => s.built);
317
+
318
+ if (servers.length === 0) {
319
+ console.log(c('yellow', '\n⚠️ No built MCP servers found.'));
320
+ console.log(c('dim', ' Run "npm run build" to build the servers first.\n'));
321
+ process.exit(1);
322
+ }
323
+
324
+ console.log(c('bright', '\n🔧 MCP Configuration Generator\n'));
325
+ console.log(c('dim', '─'.repeat(50)));
326
+
327
+ // Let user select a server
328
+ const selected = await promptSelect('Select an MCP server:', servers);
329
+
330
+ // Get the global npm path
331
+ const globalRoot = getGlobalNpmRoot();
332
+ let serverPath;
333
+
334
+ if (globalRoot) {
335
+ // Use global path for installed package
336
+ serverPath = `${globalRoot}/@deriv-com/fe-mcp-servers/dist/${selected.name}/mcp-server.js`;
337
+ } else {
338
+ // Fallback to local path
339
+ serverPath = selected.path;
340
+ }
341
+
342
+ // Generate config
343
+ const config = {
344
+ mcpServers: {
345
+ [selected.name]: {
346
+ command: 'node',
347
+ args: [serverPath]
348
+ }
349
+ }
350
+ };
351
+
352
+ const configJson = JSON.stringify(config, null, 2);
353
+
354
+ // Create temp file
355
+ const tempDir = os.tmpdir();
356
+ const fileName = 'fe-mcp-config.txt';
357
+ const filePath = path.join(tempDir, fileName);
358
+
359
+ // Write config to file
360
+ fs.writeFileSync(filePath, configJson, 'utf-8');
361
+
362
+ console.log(c('green', `\n✅ Configuration generated for ${c('cyan', selected.name)}`));
363
+ console.log(c('dim', '─'.repeat(50)));
364
+ console.log(c('bright', '\nConfiguration:\n'));
365
+ console.log(c('cyan', configJson));
366
+ console.log(c('dim', '\n─'.repeat(50)));
367
+ console.log(c('dim', `\nFile saved to: ${filePath}`));
368
+ console.log(c('bright', '\n📂 Opening file for copy-paste...\n'));
369
+
370
+ // Open the file
371
+ openFile(filePath);
372
+ }
373
+
374
+ // Parse command line arguments
375
+ const args = process.argv.slice(2);
376
+ const command = args[0];
377
+
378
+ switch (command) {
379
+ case 'list':
380
+ case 'ls':
381
+ case undefined:
382
+ listServers();
383
+ break;
384
+
385
+ case 'code':
386
+ generateConfigFile();
387
+ break;
388
+
389
+ case 'info':
390
+ case 'show':
391
+ if (!args[1]) {
392
+ console.log(c('red', '\n❌ Please specify a server name.'));
393
+ console.log(c('dim', ' Example: fe-mcp info shift-ai\n'));
394
+ process.exit(1);
395
+ }
396
+ showInfo(args[1]);
397
+ break;
398
+
399
+ case 'config':
400
+ if (!args[1]) {
401
+ console.log(c('red', '\n❌ Please specify a server name.'));
402
+ console.log(c('dim', ' Example: fe-mcp config shift-ai\n'));
403
+ process.exit(1);
404
+ }
405
+ outputConfig(args[1]);
406
+ break;
407
+
408
+ case 'help':
409
+ case '--help':
410
+ case '-h':
411
+ showHelp();
412
+ break;
413
+
414
+ case 'version':
415
+ case '--version':
416
+ case '-v':
417
+ console.log(`${packageJson.name} v${packageJson.version}`);
418
+ break;
419
+
420
+ default:
421
+ console.log(c('red', `\n❌ Unknown command: ${command}`));
422
+ showHelp();
423
+ process.exit(1);
424
+ }