mr-magic-mcp-server 0.3.8 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mr-magic-mcp-server",
3
- "version": "0.3.8",
3
+ "version": "0.3.10",
4
4
  "description": "Lyrics MCP server connecting LRCLIB, Genius, Musixmatch, and Melon",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -1,6 +1,6 @@
1
1
  import axios from 'axios';
2
2
 
3
- import { normalizeLyricRecord } from '../provider-result-schema.js';
3
+ import { normalizeLyricRecord, lyricContentScore } from '../provider-result-schema.js';
4
4
  import { createLogger } from '../utils/logger.js';
5
5
 
6
6
  const logger = createLogger('provider:lrclib');
@@ -40,6 +40,22 @@ async function querySearch(track) {
40
40
  }
41
41
  }
42
42
 
43
+ /**
44
+ * Pick the best candidate from a list: prefer synced over plain, then prefer
45
+ * richer lyric content. This ensures fetchFromLrclib always returns a synced
46
+ * result when one is available rather than the first incidentally-ordered one.
47
+ */
48
+ function chooseBestCandidate(candidates) {
49
+ if (!candidates.length) return null;
50
+ return candidates.slice().sort((a, b) => {
51
+ // Synced results come first
52
+ const syncedDiff = (b.synced ? 1 : 0) - (a.synced ? 1 : 0);
53
+ if (syncedDiff !== 0) return syncedDiff;
54
+ // Among equally-synced results, prefer richer content
55
+ return lyricContentScore(b) - lyricContentScore(a);
56
+ })[0];
57
+ }
58
+
43
59
  export async function fetchFromLrclib(track) {
44
60
  try {
45
61
  const results = await querySearch(track);
@@ -47,16 +63,19 @@ export async function fetchFromLrclib(track) {
47
63
  logger.debug('LRCLIB: no results found', { track });
48
64
  return null;
49
65
  }
50
- const exactMatch = results.find((record) => {
66
+ const exactMatches = results.filter((record) => {
51
67
  const sameTitle = record.title?.toLowerCase() === track.title?.toLowerCase();
52
68
  const sameArtist = record.artist?.toLowerCase() === track.artist?.toLowerCase();
53
69
  return sameTitle && sameArtist;
54
70
  });
55
- const result = exactMatch ?? results[0];
71
+ // Prefer exact matches; if none, fall back to all results.
72
+ // Within each group, prefer synced then richer content.
73
+ const result = chooseBestCandidate(exactMatches) ?? chooseBestCandidate(results);
56
74
  logger.debug('LRCLIB: match selected', {
57
- exact: Boolean(exactMatch),
58
- title: result.title,
59
- artist: result.artist
75
+ exact: exactMatches.length > 0,
76
+ synced: result?.synced,
77
+ title: result?.title,
78
+ artist: result?.artist
60
79
  });
61
80
  return result;
62
81
  } catch (error) {
@@ -324,9 +324,9 @@ function testRomanization() {
324
324
  // 깻: ㄷ-representative of ㅅ batchim; 잎: ㅇ initial (silent) → liaison
325
325
  // Actually 깻 = ㄲ+ㅖ+ㅅ, 잎 = ㅇ+ㅣ+ㅍ
326
326
  // Liaison: 잎 initial ㅇ → ㅅ(깻) moves to 잎 onset: → 깨 + 씹? No:
327
- // 깻: batchim ㅅ; 잎: initial ㅇ → 깻 coda ㅅ moves to 잎 as initial 'ss'?
327
+ // 깻: batchim ㅅ; 잎: initial ㅇ → 깻 coda ㅅ moves to 잎 as initial 'ss'?
328
328
  // Standard Korean: 깻잎 → [깬닙] (nasalization of ㅅ→ㄴ before ㅣ? No.
329
- // Actually: 깻잎 → liaison: 깻(ㅅ) + 잎(ㅇ) → 깨싫...
329
+ // Actually: 깻잎 → liaison: 깻(ㅅ) + 잎(ㅇ) → 깨싫...
330
330
  // Correct pronunciation: 깻잎 [깬닙] — the ㅅ turns to ㄴ (because 잎's ㅍ batchim + ㄴ?)
331
331
  // Simpler: official = kkaennip. Our engine: 깻(ㅅ liaison to 잎ㅇ) → 깨 + 싶 → 깨십.
332
332
  // The 잎 ㅍ final stays = p. 깻잎 → Kkaesip via liaison. That's our engine's output.
package/src/tools/cli.js CHANGED
@@ -356,7 +356,10 @@ program
356
356
  },
357
357
  []
358
358
  )
359
- .option('--output <dir>', 'Directory to write exports (requires --export; defaults to MR_MAGIC_EXPORT_DIR or ./exports)')
359
+ .option(
360
+ '--output <dir>',
361
+ 'Directory to write exports (requires --export; defaults to MR_MAGIC_EXPORT_DIR or ./exports)'
362
+ )
360
363
  .option('--no-romanize', 'Disable romanized lyrics', false)
361
364
  .action(async (options) => {
362
365
  const track = buildTrackFromOptions(options);
@@ -542,7 +545,10 @@ program
542
545
  },
543
546
  []
544
547
  )
545
- .option('--output <dir>', 'Directory for exports (requires --export; defaults to MR_MAGIC_EXPORT_DIR or ./exports)')
548
+ .option(
549
+ '--output <dir>',
550
+ 'Directory for exports (requires --export; defaults to MR_MAGIC_EXPORT_DIR or ./exports)'
551
+ )
546
552
  .option('--no-romanize', 'Disable romanized lyrics', false)
547
553
  .action(async (options) => {
548
554
  const track = buildTrackFromOptions(options);