mr-magic-mcp-server 0.3.10 → 0.3.12

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/.env.example CHANGED
@@ -5,25 +5,26 @@
5
5
  # ───────────────────────────────────────────────────────────────────────────────
6
6
  # There are three ways to supply the Genius token (tried in order):
7
7
  #
8
+ # Direct token (env var, see debug section below)) — static bearer token, no auto-refresh
9
+ # Set GENIUS_DIRECT_TOKEN. Works everywhere but the token must be
10
+ # manually updated on expiry (or redeploy with a new value).
11
+ # Use this only when client_credentials are unavailable.
12
+ #
8
13
  # Auto-refresh (recommended for all deployments, including Render/ephemeral)
9
14
  # Set GENIUS_CLIENT_ID + GENIUS_CLIENT_SECRET. The server calls the Genius
10
15
  # OAuth client_credentials endpoint at runtime and refreshes the token in
11
16
  # memory automatically. No disk, no scripts, no manual token copying needed.
12
17
  #
13
- # Direct token (env var) — static bearer token, no auto-refresh
14
- # Set GENIUS_DIRECT_TOKEN. Works everywhere but the token must be
15
- # manually updated on expiry (or redeploy with a new value).
16
- # Use this only when client_credentials are unavailable.
17
- #
18
18
  # Cache token (on-disk) — local instance or persistent servers only
19
19
  # Run `npm run fetch:genius-token` to write a token to
20
20
  # `.cache/genius-token.json`. The server reads it on startup when a
21
21
  # persistent, writable filesystem is available. Not suitable for
22
22
  # ephemeral hosts.
23
+ #
23
24
  # ═══════════════════════════════════════════════════════════════════════════════
25
+
24
26
  GENIUS_CLIENT_ID=
25
27
  GENIUS_CLIENT_SECRET=
26
- GENIUS_DIRECT_TOKEN=
27
28
 
28
29
  # ═══════════════════════════════════════════════════════════════════════════════
29
30
  # REQUIRED (feature-specific) — Musixmatch
@@ -32,7 +33,7 @@ GENIUS_DIRECT_TOKEN=
32
33
  # ───────────────────────────────────────────────────────────────────────────────
33
34
  # Token resolution priority (1 = highest):
34
35
  #
35
- # 1. MUSIXMATCH_DIRECT_TOKEN (env var)
36
+ # 1. MUSIXMATCH_DIRECT_TOKEN (env var, see debug section below)
36
37
  # Static bearer token. Works everywhere but must be manually updated on
37
38
  # expiry. Use when no other option is available.
38
39
  #
@@ -47,34 +48,22 @@ GENIUS_DIRECT_TOKEN=
47
48
  # Run `npm run fetch:musixmatch-token` — writes .cache/musixmatch-token.json.
48
49
  # The server reads it on startup when a writable filesystem is available.
49
50
  # ═══════════════════════════════════════════════════════════════════════════════
50
- MUSIXMATCH_DIRECT_TOKEN=
51
-
52
- # KV store for Musixmatch token persistence (ephemeral deployments / npx installs)
53
- # Priority: Upstash Redis (priority 1) → Cloudflare KV (priority 2)
54
- # If both are set, Upstash Redis is used and Cloudflare KV is ignored.
55
51
  #
56
- # Upstash Redis: reuse the same creds as the export backend (see below) — no extra setup.
57
- # Cloudflare KV: set the three CF_* vars; create a namespace at dash.cloudflare.com.
58
- CF_API_TOKEN= # Cloudflare API token with KV:Edit permission (CF KV only)
59
- CF_ACCOUNT_ID= # Cloudflare account ID (CF KV only)
60
- CF_KV_NAMESPACE_ID= # KV namespace ID to store the token in (CF KV only)
61
- # Optional overrides (defaults are sensible for most users)
62
- MUSIXMATCH_TOKEN_KV_KEY= # KV key name (default: mr-magic:musixmatch-token)
63
- MUSIXMATCH_TOKEN_KV_TTL_SECONDS= # Token TTL in seconds (default: 2592000 = 30 days)
64
-
65
52
  # Headless / Render deployment — push a pre-captured token without opening a browser.
66
53
  # run `npm run fetch:musixmatch-token` or sign into Musixmatch manually to get a token, then set it in the env to push it to the KV store without browser automation.
67
54
  # Set MUSIXMATCH_DIRECT_TOKEN to the full musixmatchUserToken JSON payload (from fetch:musixmatch-token output),
55
+ #
68
56
  # then run `npm run push:musixmatch-token` or prepend it to the start command:
69
57
  # npm run push:musixmatch-token && npm run server:mcp:http
70
58
  # If unset, push:musixmatch-token exits silently (safe no-op in start commands).
71
59
  # MUSIXMATCH_DIRECT_TOKEN also serves as the runtime token override (highest env priority).
72
-
60
+ #
73
61
  # ═══════════════════════════════════════════════════════════════════════════════
74
62
  # REQUIRED (feature-specific) — Airtable
75
63
  # Only needed if you use the push_catalog_to_airtable tool.
76
64
  # Get from https://airtable.com/create/tokens
77
65
  # ═══════════════════════════════════════════════════════════════════════════════
66
+
78
67
  AIRTABLE_PERSONAL_ACCESS_TOKEN=
79
68
 
80
69
  # ═══════════════════════════════════════════════════════════════════════════════
@@ -86,25 +75,36 @@ AIRTABLE_PERSONAL_ACCESS_TOKEN=
86
75
  # inline — embeds payload directly in the MCP response (no storage needed)
87
76
  # redis — stores in Upstash Redis KV store; best for production/ephemeral hosts
88
77
  # ═══════════════════════════════════════════════════════════════════════════════
78
+
89
79
  MR_MAGIC_EXPORT_BACKEND= # local | inline | redis
90
80
 
91
- # Required when BACKEND=local
92
- MR_MAGIC_EXPORT_DIR= # Absolute path to the directory to write export files into
81
+ # KV store for Musixmatch token persistence (ephemeral deployments / npx installs)
82
+ # Priority: Upstash Redis (priority 1) Cloudflare KV (priority 2)
83
+ # If both are set, Upstash Redis is used and Cloudflare KV is ignored.
93
84
 
94
- # Required when BACKEND=redis — get from https://console.upstash.com/redis/rest
85
+ # Note: Upstash Redis is required when BACKEND=redis — get from https://console.upstash.com/redis/rest
95
86
  # These same credentials are also used by the Musixmatch KV token store, so
96
87
  # setting them once enables both features.
97
88
  UPSTASH_REDIS_REST_URL=
98
89
  UPSTASH_REDIS_REST_TOKEN=
99
90
 
91
+ # Cloudflare KV: set the three CF_* vars; create a namespace at dash.cloudflare.com.
92
+ CF_API_TOKEN= # Cloudflare API token with KV:Edit permission (CF KV only)
93
+ CF_ACCOUNT_ID= # Cloudflare account ID (CF KV only)
94
+ CF_KV_NAMESPACE_ID= # KV namespace ID to store the token in (CF KV only)
95
+
96
+ # Required when BACKEND=local
97
+ MR_MAGIC_EXPORT_DIR=/Users/yourusername/Downloads/magic-export/ # Absolute path to the directory to write export files into
98
+
100
99
  # Base URL the server uses to build download links returned to the MCP client.
101
- # Examples: https://yourserver.com | http://localhost:3444
100
+ # Examples: https://yourserver.com | http://localhost:3444 | http://localhost:3333
102
101
  # See README for details.
103
102
  MR_MAGIC_DOWNLOAD_BASE_URL=
104
103
 
105
104
  # ═══════════════════════════════════════════════════════════════════════════════
106
105
  # Server & Runtime
107
106
  # ═══════════════════════════════════════════════════════════════════════════════
107
+
108
108
  PORT= # Override all server ports (default: 3444 MCP / 3333 HTTP automation)
109
109
  LOG_LEVEL= # error | warn | info | debug (default: info)
110
110
  MR_MAGIC_QUIET_STDIO=0 # Set to 1 to suppress non-error stdout logs (recommended under stdio MCP clients)
@@ -113,6 +113,7 @@ MR_MAGIC_HTTP_TIMEOUT_MS=10000 # Global outbound HTTP timeout in ms (default: 1
113
113
  # ═══════════════════════════════════════════════════════════════════════════════
114
114
  # Optional — Melon provider
115
115
  # ═══════════════════════════════════════════════════════════════════════════════
116
+
116
117
  MELON_COOKIE= # Pin a browser session cookie for consistent results; anonymous access usually works without it
117
118
 
118
119
  # ═══════════════════════════════════════════════════════════════════════════════
@@ -130,10 +131,19 @@ MR_MAGIC_EXPORT_TTL_SECONDS=3600
130
131
  GENIUS_TOKEN_CACHE=.cache/genius-token.json
131
132
  MUSIXMATCH_TOKEN_CACHE=.cache/musixmatch-token.json
132
133
 
134
+ # Direct tokens — Use these as temp overrides for testing or debugging, but for long-term use the auto-refresh or KV options are recommended to avoid manual token management.
135
+ # Set GENIUS_DIRECT_TOKEN and MUSIXMATCH_DIRECT_TOKEN to the full token payloads (from fetch-genius-token and fetch-musixmatch-token output), then restart the server.
136
+ GENIUS_DIRECT_TOKEN=
137
+ MUSIXMATCH_DIRECT_TOKEN=
138
+
133
139
  # Musixmatch: when 1, provider will attempt to re-run the fetch script
134
140
  # automatically (headless) if no token is available
135
141
  MUSIXMATCH_AUTO_FETCH=0
136
142
 
143
+ # Optional overrides for Musixmatch KV token storage (only needed if using KV storage and want to customize beyond the defaults)
144
+ MUSIXMATCH_TOKEN_KV_KEY= # KV key name (default: mr-magic:musixmatch-token)
145
+ MUSIXMATCH_TOKEN_KV_TTL_SECONDS= # Token TTL in seconds (default: 2592000 = 30 days)
146
+
137
147
  # Playwright Headless mode toggle for fetch scripts that require browser automation (default: false)
138
148
  HEADLESS=
139
149
 
package/README.md CHANGED
@@ -609,19 +609,141 @@ Responses:
609
609
 
610
610
  Both the stdio and Streamable HTTP & SSE transports expose the same tool registry:
611
611
 
612
- | Tool | Purpose |
613
- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
614
- | `find_lyrics` | Fetch best lyrics (prefers synced) plus metadata and payload. |
615
- | `find_synced_lyrics` | Like `find_lyrics` but rejects plain-only results. |
616
- | `search_lyrics` | List candidate matches across all providers without downloading lyrics. |
617
- | `search_provider` | Query a single named provider. |
618
- | `get_provider_status` | Report readiness and notes for each provider. |
619
- | `format_lyrics` | Format lyrics in memory (optional romanization) for display. |
620
- | `export_lyrics` | Write plain / LRC / SRT / romanized files to the export backend. |
621
- | `select_match` | Pick a prior result by provider, index, or synced flag. |
622
- | `build_catalog_payload` | Return a compact record (title / link / lyrics) for Airtable-style inserts. |
623
- | `push_catalog_to_airtable` | Write catalog records to Airtable server-side — lyrics never pass through LLM arguments. Requires `AIRTABLE_PERSONAL_ACCESS_TOKEN`. |
624
- | `runtime_status` | Snapshot provider readiness plus relevant env vars. |
612
+ | Tool | Purpose |
613
+ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
614
+ | `find_lyrics` | Fetch best lyrics (prefers synced) plus metadata and payload from direct track metadata, or resolve a prior provider reference / selected match. |
615
+ | `find_synced_lyrics` | Like `find_lyrics` but rejects plain-only results; also accepts direct track metadata, a provider reference, or a selected match. |
616
+ | `search_lyrics` | Return grouped, preview-only candidate matches across all providers, including reusable provider references. |
617
+ | `search_provider` | Query a single named provider and return flat, preview-only matches plus reusable provider references. |
618
+ | `get_provider_status` | Report readiness and notes for each provider. |
619
+ | `format_lyrics` | Format lyrics in memory (optional romanization) for display from direct track metadata, a provider reference, or a selected match. |
620
+ | `export_lyrics` | Write plain / LRC / SRT / romanized files to the export backend from direct track metadata, a provider reference, or a selected match. |
621
+ | `select_match` | Pick a single prior search result from grouped `items`, flat `matches`, or a direct `match`, using provider/index/synced filters. |
622
+ | `build_catalog_payload` | Return a compact record (title / link / lyrics) for Airtable-style inserts from direct track metadata, a provider reference, or a selected match. |
623
+ | `push_catalog_to_airtable` | Write catalog records to Airtable server-side — lyrics never pass through LLM arguments. Requires `AIRTABLE_PERSONAL_ACCESS_TOKEN`. |
624
+ | `runtime_status` | Snapshot provider readiness plus relevant env vars. |
625
+
626
+ ### MCP search result behavior
627
+
628
+ `search_lyrics` and `search_provider` now return **preview-only** search results. They are intentionally compact and do **not** include full lyrics or raw provider payloads.
629
+
630
+ Each returned result is designed for follow-up tool calls and includes:
631
+
632
+ - core metadata such as provider, title, artist, album, duration, source URL, confidence, and synced/plain-only flags
633
+ - preview snippets (`plainPreview` / `syncedPreview`) when available
634
+ - a reusable `reference` object that can be passed back into downstream tools for exact result recall
635
+
636
+ The `reference` object is the durable handoff between search and resolution. Instead of copying large provider payloads around, clients can reuse the compact reference to resolve the exact provider result later.
637
+
638
+ ### MCP provider-reference workflow
639
+
640
+ After a search step, the following tools can resolve lyrics from **either**:
641
+
642
+ - direct `track` metadata
643
+ - a provider `reference` returned by `search_lyrics` or `search_provider`
644
+ - a selected `match` object from prior search output
645
+
646
+ This applies to:
647
+
648
+ - `find_lyrics`
649
+ - `find_synced_lyrics`
650
+ - `build_catalog_payload`
651
+ - `format_lyrics`
652
+ - `export_lyrics`
653
+
654
+ ### `select_match` in the workflow
655
+
656
+ `select_match` is the bridge between preview-only search results and the tools that resolve full lyrics.
657
+
658
+ It accepts any of these input shapes:
659
+
660
+ - `items` — grouped provider results returned by `search_lyrics`
661
+ - `matches` — flat results returned by `search_provider` (or other flattened match arrays)
662
+ - `match` — a single previously returned result passed through directly
663
+
664
+ Use `criteria` to narrow the choice by provider, require synced lyrics, and/or choose a zero-based index. The returned selected match can then be passed directly into `find_lyrics`, `find_synced_lyrics`, `build_catalog_payload`, `format_lyrics`, or `export_lyrics`. If you already know which result you want, you can skip `select_match` and pass that result's `reference` directly.
665
+
666
+ ### Example workflow: search → select → build/export
667
+
668
+ 1. Call `search_lyrics` to get grouped, preview-only results plus provider `reference` objects.
669
+ 2. Call `select_match` with the returned `items` to choose one result.
670
+ 3. Pass the returned `match` into `build_catalog_payload`, `find_lyrics`, `format_lyrics`, or `export_lyrics`.
671
+ 4. Optionally reuse the nested `match.result.reference` later to resolve the exact same provider result again without repeating the original broad search.
672
+
673
+ Example sequence:
674
+
675
+ ```json
676
+ {
677
+ "track": { "artist": "Coldplay", "title": "Yellow" }
678
+ }
679
+ ```
680
+
681
+ → `search_lyrics`
682
+
683
+ ```json
684
+ {
685
+ "items": [
686
+ {
687
+ "provider": "lrclib",
688
+ "results": [
689
+ {
690
+ "title": "Yellow",
691
+ "artist": "Coldplay",
692
+ "synced": true,
693
+ "reference": {
694
+ "provider": "lrclib",
695
+ "providerId": "...",
696
+ "fingerprint": "..."
697
+ }
698
+ }
699
+ ]
700
+ }
701
+ ],
702
+ "criteria": { "requireSynced": true, "index": 0 }
703
+ }
704
+ ```
705
+
706
+ → `select_match`
707
+
708
+ ```json
709
+ {
710
+ "match": {
711
+ "provider": "lrclib",
712
+ "result": {
713
+ "title": "Yellow",
714
+ "artist": "Coldplay",
715
+ "reference": {
716
+ "provider": "lrclib",
717
+ "providerId": "...",
718
+ "fingerprint": "..."
719
+ }
720
+ }
721
+ },
722
+ "options": {
723
+ "omitInlineLyrics": true,
724
+ "lyricsPayloadMode": "payload"
725
+ }
726
+ }
727
+ ```
728
+
729
+ → `build_catalog_payload`
730
+
731
+ Or export the same exact result later with:
732
+
733
+ ```json
734
+ {
735
+ "reference": {
736
+ "provider": "lrclib",
737
+ "providerId": "...",
738
+ "fingerprint": "..."
739
+ },
740
+ "options": {
741
+ "formats": ["plain", "lrc", "srt"]
742
+ }
743
+ }
744
+ ```
745
+
746
+ → `export_lyrics`
625
747
 
626
748
  ## Airtable Integration
627
749
 
@@ -1199,6 +1321,8 @@ curl -sS -X POST http://127.0.0.1:3444/mcp \
1199
1321
 
1200
1322
  #### `find_lyrics`
1201
1323
 
1324
+ Accepts either direct `track` metadata, a provider `reference` from prior search output, or a selected `match`.
1325
+
1202
1326
  ```bash
1203
1327
  curl -sS -X POST http://127.0.0.1:3444/mcp \
1204
1328
  -H 'Content-Type: application/json' \
@@ -1208,6 +1332,8 @@ curl -sS -X POST http://127.0.0.1:3444/mcp \
1208
1332
 
1209
1333
  #### `find_synced_lyrics`
1210
1334
 
1335
+ Same lookup inputs as `find_lyrics`, but only returns timestamped results.
1336
+
1211
1337
  ```bash
1212
1338
  curl -sS -X POST http://127.0.0.1:3444/mcp \
1213
1339
  -H 'Content-Type: application/json' \
@@ -1217,6 +1343,8 @@ curl -sS -X POST http://127.0.0.1:3444/mcp \
1217
1343
 
1218
1344
  #### `build_catalog_payload` — inline lyrics
1219
1345
 
1346
+ Accepts direct `track` metadata, a reusable `reference`, or a selected `match` from prior search output.
1347
+
1220
1348
  ```bash
1221
1349
  curl -sS -X POST http://127.0.0.1:3444/mcp \
1222
1350
  -H 'Content-Type: application/json' \
@@ -1241,6 +1369,8 @@ curl -sS -X POST http://127.0.0.1:3444/mcp \
1241
1369
 
1242
1370
  #### `search_lyrics`
1243
1371
 
1372
+ Returns grouped, preview-only results under `items`. Each result includes a reusable `reference`, plus compact metadata and previews when available. Full lyrics and raw provider payloads are intentionally omitted.
1373
+
1244
1374
  ```bash
1245
1375
  curl -sS -X POST http://127.0.0.1:3444/mcp \
1246
1376
  -H 'Content-Type: application/json' \
@@ -1250,6 +1380,8 @@ curl -sS -X POST http://127.0.0.1:3444/mcp \
1250
1380
 
1251
1381
  #### `search_provider`
1252
1382
 
1383
+ Returns a flat `matches` array with the same preview-only result shape used by the grouped `search_lyrics` output.
1384
+
1253
1385
  ```bash
1254
1386
  curl -sS -X POST http://127.0.0.1:3444/mcp \
1255
1387
  -H 'Content-Type: application/json' \
@@ -1259,6 +1391,8 @@ curl -sS -X POST http://127.0.0.1:3444/mcp \
1259
1391
 
1260
1392
  #### `format_lyrics`
1261
1393
 
1394
+ Accepts direct `track` metadata, a reusable `reference`, or a selected `match`.
1395
+
1262
1396
  ```bash
1263
1397
  curl -sS -X POST http://127.0.0.1:3444/mcp \
1264
1398
  -H 'Content-Type: application/json' \
@@ -1268,6 +1402,8 @@ curl -sS -X POST http://127.0.0.1:3444/mcp \
1268
1402
 
1269
1403
  #### `export_lyrics`
1270
1404
 
1405
+ Accepts direct `track` metadata, a reusable `reference`, or a selected `match`.
1406
+
1271
1407
  ```bash
1272
1408
  curl -sS -X POST http://127.0.0.1:3444/mcp \
1273
1409
  -H 'Content-Type: application/json' \
@@ -1321,6 +1457,8 @@ curl -sS -X POST http://127.0.0.1:3444/mcp \
1321
1457
 
1322
1458
  #### `select_match`
1323
1459
 
1460
+ Use `items` for grouped `search_lyrics` output, `matches` for flat `search_provider` output, or `match` to pass through a single result directly.
1461
+
1324
1462
  ```bash
1325
1463
  curl -sS -X POST http://127.0.0.1:3444/mcp \
1326
1464
  -H 'Content-Type: application/json' \
@@ -1334,6 +1472,21 @@ curl -sS -X POST http://127.0.0.1:3444/mcp \
1334
1472
  }' | jq
1335
1473
  ```
1336
1474
 
1475
+ Example using grouped `items` from `search_lyrics`:
1476
+
1477
+ ```bash
1478
+ curl -sS -X POST http://127.0.0.1:3444/mcp \
1479
+ -H 'Content-Type: application/json' \
1480
+ -H 'Accept: application/json, text/event-stream' \
1481
+ -d '{
1482
+ "jsonrpc":"2.0","id":14,"method":"tools/call",
1483
+ "params":{"name":"select_match","arguments":{
1484
+ "items":[{"provider":"lrclib","results":[{"title":"Yellow","artist":"Coldplay","synced":true,"reference":{"provider":"lrclib","providerId":"123","fingerprint":"abc"}}]}],
1485
+ "criteria":{"requireSynced":true,"index":0}
1486
+ }}
1487
+ }' | jq
1488
+ ```
1489
+
1337
1490
  > **MCP tool response shape:**
1338
1491
  >
1339
1492
  > - `result.structuredContent` — machine-friendly object (all fields, full values)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mr-magic-mcp-server",
3
- "version": "0.3.10",
3
+ "version": "0.3.12",
4
4
  "description": "Lyrics MCP server connecting LRCLIB, Genius, Musixmatch, and Melon",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -21,6 +21,8 @@
21
21
  ".env.example"
22
22
  ],
23
23
  "scripts": {
24
+ "check-for-updates": "npx npm-check-updates",
25
+ "update-deps": "npx npm-check-updates -u && npm install",
24
26
  "cleanup": "eslint . --fix && prettier --write .",
25
27
  "cli": "node src/bin/cli.js",
26
28
  "server:http": "node src/bin/http-server.js",
@@ -59,18 +61,18 @@
59
61
  "node": ">=20"
60
62
  },
61
63
  "dependencies": {
62
- "@dotenvx/dotenvx": "^1.55.1",
63
- "@modelcontextprotocol/sdk": "^1.27.1",
64
- "axios": "^1.13.6",
64
+ "@dotenvx/dotenvx": "^1.61.1",
65
+ "@modelcontextprotocol/sdk": "^1.29.0",
66
+ "axios": "^1.15.0",
65
67
  "cheerio": "^1.2.0",
66
68
  "commander": "^14.0.3"
67
69
  },
68
70
  "devDependencies": {
69
- "eslint": "^10.0.3",
71
+ "eslint": "^10.2.1",
70
72
  "eslint-config-prettier": "^10.1.8",
71
- "eslint-plugin-import-x": "^4.15.1",
72
- "playwright": "^1.58.2",
73
- "prettier": "^3.8.1"
73
+ "eslint-plugin-import-x": "^4.16.2",
74
+ "playwright": "^1.59.1",
75
+ "prettier": "^3.8.3"
74
76
  },
75
77
  "overrides": {
76
78
  "entities": "^7.0.1",