mr-magic-mcp-server 0.3.12 → 0.4.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
@@ -1161,12 +1161,19 @@ A single CLI entrypoint (`mrmagic-cli`) is published with the package. Inside th
1161
1161
  local repo use `npm run cli -- <subcommand>` unless you have run `npm link` or
1162
1162
  installed globally.
1163
1163
 
1164
+ Global CLI options:
1165
+
1166
+ - `--env-path <path>` / `--env-file <path>` — load credentials from a custom `.env` file
1167
+ before running the command. This is useful for global installs, `npm link`, and `npx`
1168
+ usage where the package install directory is not your project directory.
1169
+
1164
1170
  ### Commands
1165
1171
 
1166
1172
  | Command | Purpose | Notable flags |
1167
1173
  | ----------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
1168
1174
  | `mrmagic-cli search` | List candidates across providers without downloading. | `--artist`, `--title`, `--provider`, `--duration`, `--show-all`, `--pick` |
1169
1175
  | `mrmagic-cli find` | Resolve best lyric (prefers synced) and print / export. | `--providers`, `--synced-only`, `--export`, `--format`, `--output`, `--no-romanize`, `--choose`, `--index` |
1176
+ | `mrmagic-cli export` | Resolve best lyric and write export files directly. | `--providers`, `--synced-only`, `--format`, `--output`, `--no-romanize` |
1170
1177
  | `mrmagic-cli select` | Pick first match from a prioritized provider list. | `--providers`, `--artist`, `--title`, `--require-synced` |
1171
1178
  | `mrmagic-cli server` | Start the JSON automation API. | `--host`, `--port`, `--remote` |
1172
1179
  | `mrmagic-cli server:mcp` | Start the MCP stdio server. | — |
@@ -1180,9 +1187,15 @@ installed globally.
1180
1187
  # Search all providers
1181
1188
  npm run cli -- search --artist "BLACKPINK" --title "Kill This Love"
1182
1189
 
1190
+ # Global/custom install with an explicit credential file
1191
+ mrmagic-cli --env-path /absolute/path/to/.env find --artist "Nayeon" --title "POP!"
1192
+
1183
1193
  # Find best lyric (prefers synced LRC)
1184
1194
  npm run cli -- find --artist "Nayeon" --title "POP!"
1185
1195
 
1196
+ # Export plain text and SRT files for the best match
1197
+ npm run cli -- export --artist "Nayeon" --title "POP!" --format plain --format srt --output ./exports
1198
+
1186
1199
  # Pick first synced match from a prioritized provider list
1187
1200
  npm run cli -- select --providers lrclib,genius --artist "Nayeon" --title "POP!" --require-synced
1188
1201
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mr-magic-mcp-server",
3
- "version": "0.3.12",
3
+ "version": "0.4.0",
4
4
  "description": "Lyrics MCP server connecting LRCLIB, Genius, Musixmatch, and Melon",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -54,7 +54,7 @@ Examples:
54
54
  - `BLACKPINK, Doja Cat, Absolutely - Crazy (Lyrics)`
55
55
  - `Joji - Glimpse of Us (Lyrics)`
56
56
  - `[GANG$] - Money (Remix) (Lyrics)`
57
- - WRONG:`John Wick - This Song Is Lit (feat. BANKS) \\ RIGHT:`John Wick, BANKS - This Song Is Lit`
57
+ - WRONG:`John Wick - This Song Is Lit (feat. BANKS) \\ RIGHT: `John Wick, BANKS - This Song Is Lit`
58
58
 
59
59
  Artist names may contain brackets or special characters. Preserve them exactly.
60
60
 
@@ -85,6 +85,9 @@ Artist names may contain brackets or special characters. Preserve them exactly.
85
85
 
86
86
  `build_catalog_payload` is the **required and exclusive lyric-resolution / lyric-preparation step for any Airtable entry**.
87
87
 
88
+ If you have an issue finding a lyric for a song with all the artists given, fallback to just using the first written artist, or the native language equivalent if found on Spotify.
89
+
90
+ If you have an issue finding a lyric for a song with the english title, use the native language version native language equivalent title of the song found from Spotify.
88
91
  For every song that will be written to Airtable:
89
92
 
90
93
  1. You **must** call `build_catalog_payload` before the Lyrics field can be written.
package/src/bin/cli.js CHANGED
@@ -6,4 +6,12 @@
6
6
  if (!process.env.LOG_LEVEL) process.env.LOG_LEVEL = 'warn';
7
7
  if (!process.env.MR_MAGIC_QUIET_STDIO) process.env.MR_MAGIC_QUIET_STDIO = '1';
8
8
 
9
+ const envPathFlagIndex = process.argv.findIndex(
10
+ (arg) => arg === '--env-path' || arg === '--env-file'
11
+ );
12
+ if (envPathFlagIndex >= 0 && process.argv[envPathFlagIndex + 1]) {
13
+ process.env.MR_MAGIC_ENV_PATH = process.argv[envPathFlagIndex + 1];
14
+ process.argv.splice(envPathFlagIndex, 2);
15
+ }
16
+
9
17
  await import('../tools/cli.js');
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import assert from 'node:assert/strict';
3
+ import { execFileSync } from 'node:child_process';
4
+ import fs from 'node:fs';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
3
7
 
4
8
  import { selectMatch } from '../index.js';
5
9
  import { buildChooserEntries, autoPick } from '../core/find-service.js';
@@ -431,6 +435,54 @@ async function testBuildPayloadFromResultNoCacheKeyWhenNoLyrics() {
431
435
  console.log('buildPayloadFromResult omits lyricsCacheKey when best has no lyrics: ok');
432
436
  }
433
437
 
438
+ function testCliExportCommandHelp() {
439
+ const output = execFileSync(process.execPath, ['src/bin/cli.js', 'export', '--help'], {
440
+ encoding: 'utf8',
441
+ env: {
442
+ ...process.env,
443
+ LOG_LEVEL: 'error',
444
+ MR_MAGIC_QUIET_STDIO: '1'
445
+ }
446
+ });
447
+
448
+ assert.ok(output.includes('Find lyrics and write plain/LRC/SRT exports'));
449
+ assert.ok(output.includes('--format <format>'));
450
+ assert.ok(output.includes('--output <dir>'));
451
+
452
+ divider();
453
+ console.log('CLI export command help is available: ok');
454
+ }
455
+
456
+ function testCliEnvPathLoadsCustomEnvFile() {
457
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mrmagic-cli-env-'));
458
+ const envPath = path.join(tempDir, '.env.custom');
459
+ fs.writeFileSync(envPath, 'GENIUS_DIRECT_TOKEN=cli-env-path-token\n', 'utf8');
460
+
461
+ try {
462
+ const output = execFileSync(
463
+ process.execPath,
464
+ ['src/bin/cli.js', '--env-path', envPath, 'status'],
465
+ {
466
+ encoding: 'utf8',
467
+ env: {
468
+ PATH: process.env.PATH,
469
+ NODE_ENV: process.env.NODE_ENV,
470
+ LOG_LEVEL: 'error',
471
+ MR_MAGIC_QUIET_STDIO: '1'
472
+ }
473
+ }
474
+ );
475
+
476
+ assert.ok(output.includes('genius'));
477
+ assert.ok(output.includes('Ready'), 'custom env file should make Genius status ready');
478
+ } finally {
479
+ fs.rmSync(tempDir, { recursive: true, force: true });
480
+ }
481
+
482
+ divider();
483
+ console.log('CLI --env-path loads custom env files: ok');
484
+ }
485
+
434
486
  async function run() {
435
487
  testAutoPickPrefersSynced();
436
488
  testAutoPickFallbackWhenNoSynced();
@@ -447,6 +499,8 @@ async function run() {
447
499
  testRomanization();
448
500
  await testBuildPayloadFromResultReturnsCacheKey();
449
501
  await testBuildPayloadFromResultNoCacheKeyWhenNoLyrics();
502
+ testCliExportCommandHelp();
503
+ testCliEnvPathLoadsCustomEnvFile();
450
504
  const toolNames = mcpToolDefinitions.map((tool) => tool.name);
451
505
  console.log('MCP tooling available:', toolNames.join(', '));
452
506
  console.log('All sanity checks passed');
package/src/tools/cli.js CHANGED
@@ -107,7 +107,9 @@ const program = new Command();
107
107
  program
108
108
  .name('mrmagic-cli')
109
109
  .description('Lyrics MCP server CLI powered by LRCLIB, Genius, Musixmatch, and Melon')
110
- .version('0.1.3');
110
+ .version('0.1.3')
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');
111
113
 
112
114
  function normalizeFormatOptions(value) {
113
115
  if (!value) return [];
@@ -699,6 +701,67 @@ program
699
701
  }
700
702
  });
701
703
 
704
+ program
705
+ .command('export')
706
+ .description('Find lyrics and write plain/LRC/SRT exports')
707
+ .requiredOption('--artist <name>', 'Artist name')
708
+ .requiredOption('--title <name>', 'Song title')
709
+ .option('--album <name>', 'Album name')
710
+ .option('--duration <ms>', 'Track duration in milliseconds')
711
+ .option('--providers <list>', 'Comma-separated provider list')
712
+ .option('--synced-only', 'Require synced lyrics', false)
713
+ .option(
714
+ '--format <format>',
715
+ 'Export format (plain|lrc|srt). repeatable',
716
+ (value, acc) => {
717
+ acc.push(value);
718
+ return acc;
719
+ },
720
+ []
721
+ )
722
+ .option('--output <dir>', 'Directory for exports (defaults to MR_MAGIC_EXPORT_DIR or ./exports)')
723
+ .option('--no-romanize', 'Disable romanized lyrics', false)
724
+ .action(async (options) => {
725
+ const track = buildTrackFromOptions(options);
726
+ const searchLabel = [track.artist, track.title].filter(Boolean).join(' - ');
727
+ process.stderr.write(`Searching: ${searchLabel}...\n`);
728
+ const providerNames = options.providers
729
+ ? options.providers.split(',').map((value) => value.trim())
730
+ : [];
731
+ const result = await runFind(track, {
732
+ providerNames,
733
+ syncedOnly: options.syncedOnly
734
+ });
735
+
736
+ if (!result.best) {
737
+ console.error('No best match available');
738
+ process.exitCode = 1;
739
+ return;
740
+ }
741
+
742
+ const includeRomanization = options.noRomanize ? false : true;
743
+ const exports = await exportLyrics(result.best, {
744
+ formats: deriveFormatSet(options.format),
745
+ output: options.output,
746
+ includeRomanization
747
+ });
748
+ console.log(
749
+ JSON.stringify(
750
+ {
751
+ picked: {
752
+ provider: result.best.provider || 'unknown',
753
+ synced: Boolean(result.best.synced),
754
+ artist: result.best.artist || track.artist || 'unknown',
755
+ title: result.best.title || track.title || 'song'
756
+ },
757
+ exports
758
+ },
759
+ null,
760
+ 2
761
+ )
762
+ );
763
+ });
764
+
702
765
  program
703
766
  .command('search-provider')
704
767
  .description('Search a specific provider only')