mr-magic-mcp-server 0.4.0 → 0.5.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.
package/README.md CHANGED
@@ -401,6 +401,8 @@ The `MR_MAGIC_EXPORT_BACKEND` variable controls where formatted lyrics are store
401
401
  - **`local`** (default) — writes files to `MR_MAGIC_EXPORT_DIR` (or `exports/` when
402
402
  unset). Make sure the target directory is writable. The `export_lyrics` tool also
403
403
  returns the raw `content` field so clients can inline results when file writes fail.
404
+ For global or npx CLI usage, the default `exports/` directory is relative to the
405
+ directory where you run `mrmagic-cli`, not the npm package install directory.
404
406
 
405
407
  - **`inline`** — skips disk writes entirely. Each export is returned in the tool
406
408
  response with `content` populated and `skipped: true` to signal that persistence
@@ -435,6 +437,16 @@ Or against the JSON HTTP server:
435
437
  MR_MAGIC_DOWNLOAD_BASE_URL=http://127.0.0.1:3333
436
438
  ```
437
439
 
440
+ For global installs, start the JSON HTTP download server with the same persisted env
441
+ file or environment variables used for export commands:
442
+
443
+ ```bash
444
+ mrmagic-cli server --port 3333
445
+ ```
446
+
447
+ Download URLs are only needed for the `redis` export backend. Local exports return
448
+ `file://` URLs and write files directly to `MR_MAGIC_EXPORT_DIR` or `./exports`.
449
+
438
450
  ## Local Deployment
439
451
 
440
452
  Run whichever entrypoint you need via npm scripts:
@@ -1166,6 +1178,9 @@ Global CLI options:
1166
1178
  - `--env-path <path>` / `--env-file <path>` — load credentials from a custom `.env` file
1167
1179
  before running the command. This is useful for global installs, `npm link`, and `npx`
1168
1180
  usage where the package install directory is not your project directory.
1181
+ - `--save-env-path` — persist the provided `--env-path` to
1182
+ `~/.config/mrmagic-cli/config.json` so future `mrmagic-cli` commands load the same
1183
+ credentials file automatically.
1169
1184
 
1170
1185
  ### Commands
1171
1186
 
@@ -1190,12 +1205,25 @@ npm run cli -- search --artist "BLACKPINK" --title "Kill This Love"
1190
1205
  # Global/custom install with an explicit credential file
1191
1206
  mrmagic-cli --env-path /absolute/path/to/.env find --artist "Nayeon" --title "POP!"
1192
1207
 
1208
+ # Save that credential file path once for future global CLI commands
1209
+ mrmagic-cli --env-path /absolute/path/to/.env --save-env-path status
1210
+
1193
1211
  # Find best lyric (prefers synced LRC)
1194
1212
  npm run cli -- find --artist "Nayeon" --title "POP!"
1195
1213
 
1196
1214
  # Export plain text and SRT files for the best match
1197
1215
  npm run cli -- export --artist "Nayeon" --title "POP!" --format plain --format srt --output ./exports
1198
1216
 
1217
+ # Global install: local export files are written to ./exports relative to your shell's current directory
1218
+ mrmagic-cli export --artist "Nayeon" --title "POP!" --format srt
1219
+
1220
+ # Global install: Redis-backed download URLs served by the JSON HTTP server
1221
+ MR_MAGIC_EXPORT_BACKEND=redis \
1222
+ MR_MAGIC_DOWNLOAD_BASE_URL=http://127.0.0.1:3333 \
1223
+ mrmagic-cli export --artist "Nayeon" --title "POP!" --format srt
1224
+
1225
+ mrmagic-cli server --port 3333
1226
+
1199
1227
  # Pick first synced match from a prioritized provider list
1200
1228
  npm run cli -- select --providers lrclib,genius --artist "Nayeon" --title "POP!" --require-synced
1201
1229
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mr-magic-mcp-server",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Lyrics MCP server connecting LRCLIB, Genius, Musixmatch, and Melon",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -61,14 +61,14 @@
61
61
  "node": ">=20"
62
62
  },
63
63
  "dependencies": {
64
- "@dotenvx/dotenvx": "^1.61.1",
64
+ "@dotenvx/dotenvx": "^1.65.0",
65
65
  "@modelcontextprotocol/sdk": "^1.29.0",
66
- "axios": "^1.15.0",
66
+ "axios": "^1.16.0",
67
67
  "cheerio": "^1.2.0",
68
68
  "commander": "^14.0.3"
69
69
  },
70
70
  "devDependencies": {
71
- "eslint": "^10.2.1",
71
+ "eslint": "^10.3.0",
72
72
  "eslint-config-prettier": "^10.1.8",
73
73
  "eslint-plugin-import-x": "^4.16.2",
74
74
  "playwright": "^1.59.1",
@@ -85,7 +85,8 @@
85
85
  "hasown": "npm:@socketregistry/hasown@^1",
86
86
  "object-assign": "npm:@socketregistry/object-assign@^1",
87
87
  "safer-buffer": "npm:@socketregistry/safer-buffer@^1",
88
- "side-channel": "npm:@socketregistry/side-channel@^1"
88
+ "side-channel": "npm:@socketregistry/side-channel@^1",
89
+ "encoding-sniffer": "^1.0.2"
89
90
  },
90
91
  "resolutions": {
91
92
  "entities": "^7.0.1",
@@ -98,6 +99,7 @@
98
99
  "hasown": "npm:@socketregistry/hasown@^1",
99
100
  "object-assign": "npm:@socketregistry/object-assign@^1",
100
101
  "safer-buffer": "npm:@socketregistry/safer-buffer@^1",
101
- "side-channel": "npm:@socketregistry/side-channel@^1"
102
+ "side-channel": "npm:@socketregistry/side-channel@^1",
103
+ "encoding-sniffer": "^1.0.2"
102
104
  }
103
105
  }
package/src/bin/cli.js CHANGED
@@ -3,15 +3,54 @@
3
3
  // Reduce structured-log noise and env-missing warnings for interactive CLI usage.
4
4
  // These must be set before any module that reads them is evaluated, so we use
5
5
  // a dynamic import below instead of a static one.
6
+ import fs from 'node:fs';
7
+ import os from 'node:os';
8
+ import path from 'node:path';
9
+
6
10
  if (!process.env.LOG_LEVEL) process.env.LOG_LEVEL = 'warn';
7
11
  if (!process.env.MR_MAGIC_QUIET_STDIO) process.env.MR_MAGIC_QUIET_STDIO = '1';
8
12
 
13
+ const configPath = process.env.MR_MAGIC_CLI_CONFIG_PATH
14
+ ? path.resolve(process.env.MR_MAGIC_CLI_CONFIG_PATH)
15
+ : path.join(os.homedir(), '.config', 'mrmagic-cli', 'config.json');
16
+ const configDir = path.dirname(configPath);
17
+
18
+ function readPersistedEnvPath() {
19
+ try {
20
+ const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8'));
21
+ return typeof parsed.envPath === 'string' && parsed.envPath.trim() ? parsed.envPath : null;
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+
27
+ function persistEnvPath(envPath) {
28
+ fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
29
+ fs.writeFileSync(configPath, `${JSON.stringify({ envPath }, null, 2)}\n`, {
30
+ encoding: 'utf8',
31
+ mode: 0o600
32
+ });
33
+ }
34
+
9
35
  const envPathFlagIndex = process.argv.findIndex(
10
36
  (arg) => arg === '--env-path' || arg === '--env-file'
11
37
  );
12
38
  if (envPathFlagIndex >= 0 && process.argv[envPathFlagIndex + 1]) {
13
39
  process.env.MR_MAGIC_ENV_PATH = process.argv[envPathFlagIndex + 1];
14
40
  process.argv.splice(envPathFlagIndex, 2);
41
+ if (process.argv.includes('--save-env-path')) {
42
+ persistEnvPath(process.env.MR_MAGIC_ENV_PATH);
43
+ }
44
+ } else if (!process.env.MR_MAGIC_ENV_PATH) {
45
+ const persistedEnvPath = readPersistedEnvPath();
46
+ if (persistedEnvPath) {
47
+ process.env.MR_MAGIC_ENV_PATH = persistedEnvPath;
48
+ }
49
+ }
50
+
51
+ const saveEnvPathFlagIndex = process.argv.indexOf('--save-env-path');
52
+ if (saveEnvPathFlagIndex >= 0) {
53
+ process.argv.splice(saveEnvPathFlagIndex, 1);
15
54
  }
16
55
 
17
56
  await import('../tools/cli.js');
@@ -456,31 +456,52 @@ function testCliExportCommandHelp() {
456
456
  function testCliEnvPathLoadsCustomEnvFile() {
457
457
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mrmagic-cli-env-'));
458
458
  const envPath = path.join(tempDir, '.env.custom');
459
+ const configPath = path.join(tempDir, 'config.json');
459
460
  fs.writeFileSync(envPath, 'GENIUS_DIRECT_TOKEN=cli-env-path-token\n', 'utf8');
460
461
 
461
462
  try {
462
463
  const output = execFileSync(
463
464
  process.execPath,
464
- ['src/bin/cli.js', '--env-path', envPath, 'status'],
465
+ ['src/bin/cli.js', '--env-path', envPath, '--save-env-path', 'status'],
465
466
  {
466
467
  encoding: 'utf8',
467
468
  env: {
468
469
  PATH: process.env.PATH,
469
470
  NODE_ENV: process.env.NODE_ENV,
470
471
  LOG_LEVEL: 'error',
471
- MR_MAGIC_QUIET_STDIO: '1'
472
+ MR_MAGIC_QUIET_STDIO: '1',
473
+ MR_MAGIC_CLI_CONFIG_PATH: configPath
472
474
  }
473
475
  }
474
476
  );
475
477
 
476
478
  assert.ok(output.includes('genius'));
477
479
  assert.ok(output.includes('Ready'), 'custom env file should make Genius status ready');
480
+
481
+ const saved = JSON.parse(fs.readFileSync(configPath, 'utf8'));
482
+ assert.equal(saved.envPath, envPath, '--save-env-path should persist the selected env file');
483
+
484
+ const persistedOutput = execFileSync(process.execPath, ['src/bin/cli.js', 'status'], {
485
+ encoding: 'utf8',
486
+ env: {
487
+ PATH: process.env.PATH,
488
+ NODE_ENV: process.env.NODE_ENV,
489
+ LOG_LEVEL: 'error',
490
+ MR_MAGIC_QUIET_STDIO: '1',
491
+ MR_MAGIC_CLI_CONFIG_PATH: configPath
492
+ }
493
+ });
494
+
495
+ assert.ok(
496
+ persistedOutput.includes('Ready'),
497
+ 'CLI should reuse the persisted env path on later commands'
498
+ );
478
499
  } finally {
479
500
  fs.rmSync(tempDir, { recursive: true, force: true });
480
501
  }
481
502
 
482
503
  divider();
483
- console.log('CLI --env-path loads custom env files: ok');
504
+ console.log('CLI --env-path loads and persists custom env files: ok');
484
505
  }
485
506
 
486
507
  async function run() {
package/src/tools/cli.js CHANGED
@@ -109,7 +109,8 @@ program
109
109
  .description('Lyrics MCP server CLI powered by LRCLIB, Genius, Musixmatch, and Melon')
110
110
  .version('0.1.3')
111
111
  .option('--env-path <path>', 'Path to a .env file to load before running a CLI command')
112
- .option('--env-file <path>', 'Alias for --env-path');
112
+ .option('--env-file <path>', 'Alias for --env-path')
113
+ .option('--save-env-path', 'Persist --env-path for future CLI commands');
113
114
 
114
115
  function normalizeFormatOptions(value) {
115
116
  if (!value) return [];