mr-magic-mcp-server 0.1.12 → 0.1.13

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.
Files changed (2) hide show
  1. package/README.md +487 -817
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,22 +1,39 @@
1
1
  # Mr. Magic MCP Server
2
2
 
3
- Mr. Magic bridges LRCLIB, Genius, Musixmatch, and Melon so MCP clients, Standard
4
- HTTP automations or CLI afficionados can all request lyrics from a single toolchain.
3
+ Mr. Magic bridges LRCLIB, Genius, Musixmatch, and Melon so MCP clients, JSON HTTP
4
+ automations, and CLI aficionados can all request lyrics from a single toolchain.
5
+
6
+ ## Table of Contents
7
+
8
+ - [Prerequisites](#prerequisites)
9
+ - [Installation](#installation)
10
+ - [Environment Variables](#environment-variables)
11
+ - [Provider Credentials](#provider-credentials)
12
+ - [Export and Download Configuration](#export-and-download-configuration)
13
+ - [Local Deployment](#local-deployment)
14
+ - [Remote Deployment](#remote-deployment)
15
+ - [MCP Tools](#mcp-tools)
16
+ - [Airtable Integration](#airtable-integration)
17
+ - [MCP Client Configuration](#mcp-client-configuration)
18
+ - [CLI](#cli)
19
+ - [Manual Testing](#manual-testing)
20
+ - [Provider Notes](#provider-notes)
21
+ - [Changelog](#changelog)
22
+ - [License](#license)
5
23
 
6
24
  ## Prerequisites
7
25
 
8
26
  - Node.js 18.17 or newer
9
27
  - npm 9+
10
- - macOS/Linux/WSL (Playwright + MCP transports work cross-platform)
11
- - Provider credentials (see below)
28
+ - macOS / Linux / WSL
29
+ - Provider credentials (see [Provider Credentials](#provider-credentials))
12
30
 
13
31
  ## Installation
14
32
 
15
- ### Quick start — no clone required
33
+ ### Quick start — npx (no clone required)
16
34
 
17
- The easiest way to use Mr. Magic in an MCP client is via `npx`. No clone or
18
- local install needed — the package is fetched from npm on first run and cached
19
- locally:
35
+ The easiest way to use Mr. Magic in an MCP client is via `npx`. No clone or local
36
+ install needed — the package is fetched from npm on first run and cached locally:
20
37
 
21
38
  ```bash
22
39
  npx -y mr-magic-mcp-server
@@ -31,10 +48,10 @@ npm install -g mr-magic-mcp-server
31
48
  When installed globally, start any server directly:
32
49
 
33
50
  ```bash
34
- mcp-server # MCP stdio server (recommended for local MCP clients)
35
- mcp-http-server # Streamable HTTP MCP server
36
- http-server # JSON HTTP automation server
37
- mrmagic-cli --help # CLI
51
+ mcp-server # MCP stdio server (recommended for local MCP clients)
52
+ mcp-http-server # Streamable HTTP MCP server
53
+ http-server # JSON HTTP automation server
54
+ mrmagic-cli --help # CLI
38
55
  ```
39
56
 
40
57
  ### Local repo (development / contribution)
@@ -46,376 +63,290 @@ mrmagic-cli --help # CLI
46
63
  cd mr-magic-mcp-server
47
64
  ```
48
65
 
49
- > Alternatively, download a ZIP from GitHub, extract it, and `cd` into the
50
- > extracted directory.
51
-
52
66
  2. Install dependencies:
53
67
 
54
68
  ```bash
55
69
  npm install
56
70
  ```
57
71
 
58
- > `npm install` in this repo installs dependencies, but does **not** add
59
- > `mrmagic-cli` to your shell `PATH`.
60
- >
61
- > For local repo usage, run the CLI via `npm run cli -- ...` (or
62
- > `node src/bin/cli.js ...`). If you want direct commands like
63
- > `mrmagic-cli --help`, run `npm link` (dev symlink) or install globally.
72
+ > `npm install` does **not** add `mrmagic-cli` to your shell `PATH`.
73
+ > For local repo usage, run the CLI via `npm run cli -- ...` or `node src/bin/cli.js ...`.
74
+ > Run `npm link` (dev symlink) or install globally to get `mrmagic-cli` on `PATH`.
64
75
 
65
- 3. Configure `.env` (see Environment variables below) or export env vars in
66
- your shell before running any commands.
76
+ 3. Configure `.env` (see [Environment Variables](#environment-variables)) or export
77
+ env vars in your shell before running any commands.
67
78
 
68
79
  4. Run the desired entrypoint:
69
- - MCP Stdio server: `npm run server:mcp`
80
+
81
+ - MCP stdio server: `npm run server:mcp`
70
82
  - MCP Streamable HTTP server: `npm run server:mcp:http`
71
- - Standard JSON HTTP server: `npm run server:http`
83
+ - JSON HTTP automation server: `npm run server:http`
72
84
  - CLI: `npm run cli -- --help`
73
85
 
74
- ## Environment variables
75
-
76
- Copy `.env.example` to `.env` (or export values in your shell) and fill in the
77
- credentials plus any storage configuration:
78
-
79
- ```env
80
- PORT= # Override all server ports, or leave blank to default to 3444 for MCP, 3333 the JSON HTTP automation server.
81
- LOG_LEVEL= # Optional. error|warn|info|debug. Defaults to info.
82
- MR_MAGIC_ROOT= # Optional. Force the project root used for resolving .env/.cache paths.
83
- MR_MAGIC_ENV_PATH= # Optional. Custom path to an env file when the default isn't desired.
84
- GENIUS_CLIENT_ID= # Get from https://genius.com/api-clients, required for Genius client-credentials auth.
85
- GENIUS_CLIENT_SECRET= # Get from https://genius.com/api-clients, required for Genius client-credentials auth.
86
- GENIUS_ACCESS_TOKEN= # Get from https://genius.com/api-clients, required for Genius lyrics support when client credentials are not supplied.
87
- MUSIXMATCH_FALLBACK_TOKEN= # Fallback token (1st priority). Set as env var for production/ephemeral hosts where the filesystem is not persistent.
88
- MUSIXMATCH_ALT_FALLBACK_TOKEN= # Fallback token (2nd priority). Alternative env var; same token value, second-choice source.
89
- MUSIXMATCH_AUTO_FETCH=0 # Optional. When 1, provider will attempt to re-run the fetch script automatically (headless) if no token is available.
90
- MUSIXMATCH_TOKEN_CACHE=.cache/musixmatch-token.json
91
- MELON_COOKIE= # Optional. Pin a session cookie for consistent Melon results; anonymous access generally works without it.
92
- MR_MAGIC_EXPORT_BACKEND= # local|inline|redis
93
- MR_MAGIC_EXPORT_DIR=/absolute/path/to/exports # Required if MR_MAGIC_EXPORT_BACKEND=local
94
- MR_MAGIC_EXPORT_TTL_SECONDS=3600 # Optional, default 3600 (1 hour). Only applies to local and redis backends, ignored for inline.
95
- MR_MAGIC_DOWNLOAD_BASE_URL=https://yourserver.com|http://localhost:GIVEN_PORT # Used for generating download links for exported files. See README for details.
96
- MR_MAGIC_INLINE_PAYLOAD_MAX_CHARS=1500 # Optional, default 1500. build_catalog_payload auto-promotes payload transport to reference when omitInlineLyrics is true and lyrics exceed this threshold.
97
- MR_MAGIC_QUIET_STDIO=0 # Optional, default 0. If set to 1, suppresses all non-error logs to stdout. Recommended when running under MCP clients that read stdio (forces LOG_LEVEL=error).
98
- MR_MAGIC_HTTP_TIMEOUT_MS=10000 # Optional, default 10000. Global outbound HTTP timeout (ms) to prevent hanging provider/storage requests.
99
- MR_MAGIC_LOG_TOOL_ARGS_CHUNKS=0 # Optional, default 0. Set to 1/true to emit chunk-by-chunk MCP tool argument previews for truncation debugging.
100
- MR_MAGIC_TOOL_ARG_CHUNK_SIZE=400 # Optional, default 400. Chunk size used when MR_MAGIC_LOG_TOOL_ARGS_CHUNKS is enabled.
101
- MR_MAGIC_MCP_HTTP_DIAGNOSTICS=0 # Optional, default 0. Set to 1 to log enriched Streamable HTTP MCP request diagnostics at transport ingress.
102
- MR_MAGIC_ALLOWED_HOSTS= # Optional. Comma-separated extra hostnames for DNS rebinding protection when binding to 0.0.0.0 (e.g. custom domains). Render hostname is auto-included via RENDER_EXTERNAL_HOSTNAME.
103
- MR_MAGIC_SDK_REPRO_HTTP_DEBUG=0 # Optional, default 0. Set to 1 for verbose HTTP request/response previews in the SDK repro harness script.
104
- UPSTASH_REDIS_REST_URL= # Get from https://console.upstash.com/redis/rest, required if MR_MAGIC_EXPORT_BACKEND=redis
105
- UPSTASH_REDIS_REST_TOKEN= # Get from https://console.upstash.com/redis/rest, required if MR_MAGIC_EXPORT_BACKEND=redis
106
- AIRTABLE_PERSONAL_ACCESS_TOKEN= # Required for push_catalog_to_airtable tool. Get from https://airtable.com/create/tokens
107
- ```
86
+ ## Environment Variables
108
87
 
109
- - **Genius token sources** the server resolves the Genius token using three
110
- sources (tried in order):
111
- - **Auto-refresh** (`GENIUS_CLIENT_ID` + `GENIUS_CLIENT_SECRET`) — the server calls the
112
- Genius OAuth `client_credentials` endpoint at runtime and keeps the token refreshed
113
- in memory automatically. **Recommended for all deployments**, including Render/ephemeral
114
- hosts. No disk, no scripts, no manual token copying.
115
- - **Fallback token** (`GENIUS_ACCESS_TOKEN` env var) — a static bearer token. Works
116
- everywhere but does not auto-refresh. Use only when client_credentials are unavailable;
117
- redeploy with a new token when it expires.
118
- - **Cache token** (on-disk `.cache/genius-token.json`) — written by
119
- `npm run fetch:genius-token`. Loaded on startup when a persistent writable filesystem
120
- is available. Not suitable for ephemeral hosts.
121
- - **Musixmatch token sources** — the server resolves the Musixmatch token using
122
- two named sources, tried in order:
123
- - **Fallback token** (`MUSIXMATCH_FALLBACK_TOKEN`, then `MUSIXMATCH_ALT_FALLBACK_TOKEN`) — the
124
- token value is set directly as an environment variable. This is the
125
- recommended approach for production and ephemeral hosts (e.g. Render free
126
- tier, containers) where the filesystem cannot be relied upon between
127
- restarts. Set `MUSIXMATCH_FALLBACK_TOKEN` first; `MUSIXMATCH_ALT_FALLBACK_TOKEN` is the
128
- legacy/alternative env var for the same value.
129
- - **Cache token** (on-disk `.cache/musixmatch-token.json`) — written by the
130
- `fetch:musixmatch-token` script after a browser sign-in. Used for local
131
- development when a persistent writable filesystem is available. Not suitable
132
- for ephemeral hosts.
133
- - **MUSIXMATCH_TOKEN_CACHE** controls where the on-disk cache token file is
134
- read/written (default `<project root>/.cache/musixmatch-token.json`).
135
- - **MELON_COOKIE** is optional—anonymous access generally works, but pinning a
136
- cookie can improve consistency.
137
- - **MR_MAGIC_EXPORT_BACKEND** controls where formatted lyrics land:
138
- - `local` (default) writes to `MR_MAGIC_EXPORT_DIR` (or `exports/` when
139
- omitted).
140
- - `inline` skips disk writes and returns the formatted strings directly in
141
- the response body.
142
- - `redis` stores each export in Upstash; you must also set the `UPSTASH_*`
143
- vars plus `MR_MAGIC_DOWNLOAD_BASE_URL` so clients know which HTTP server
144
- serves `/downloads/:id/:ext`.
145
- - **MR_MAGIC_EXPORT_DIR** can be any absolute path (e.g., `/tmp/mr-magic`).
146
- Quote it only when the path contains spaces or special characters
147
- (`MR_MAGIC_EXPORT_DIR="/Users/you/My Exports"`).
148
- - **PORT** overrides both HTTP entrypoints when your platform injects one
149
- (Render, Fly, etc.). If unset, the MCP HTTP transport binds to `3444` and
150
- the JSON HTTP automation server binds to `3333`. CLI flags such as
151
- `mrmagic-cli server --port 4000` always take precedence. On Render,
152
- `PORT` is set automatically by the platform (default `10000`) — no manual
153
- override is needed.
154
- - **MR_MAGIC_DOWNLOAD_BASE_URL** should match the public URL that exposes the
155
- `/downloads` routes. Include `:port` only when the HTTP server isn’t using
156
- the default for its protocol.
157
- - **LOG_LEVEL** (error|warn|info|debug, default `info`) controls global logging verbosity.
158
- Set `LOG_LEVEL=debug` when you need verbose diagnostics.
159
- - **MR_MAGIC_QUIET_STDIO** set to `1` silences stdio transports (helpful when a
160
- host MCP client expects clean JSON over stdout). When enabled, it forces
161
- `LOG_LEVEL=error` so stdout stays quiet.
162
- - **MR_MAGIC_HTTP_TIMEOUT_MS** (default `10000`) applies a global timeout to
163
- outbound provider/export-storage network calls so slow upstream endpoints
164
- fail fast instead of hanging MCP tool calls.
165
- - **MR_MAGIC_LOG_TOOL_ARGS_CHUNKS** (default `0`) enables diagnostic chunk
166
- logging for incoming MCP `arguments` payloads. Set to `1`/`true` when
167
- debugging malformed/truncated tool calls from external clients.
168
- - **MR_MAGIC_TOOL_ARG_CHUNK_SIZE** (default `400`) controls the size of each
169
- chunk preview emitted when chunk logging is enabled.
170
- - **MR_MAGIC_INLINE_PAYLOAD_MAX_CHARS** (default `1500`) controls when
171
- `build_catalog_payload` auto-promotes payload transport to `reference`
172
- in compact Airtable-safe flows to reduce large inline lyric blobs.
173
- - **MR_MAGIC_MCP_HTTP_DIAGNOSTICS** (default `0`) enables detailed request
174
- metadata logging at the Streamable HTTP transport boundary (method, content
175
- type, body shape/length, session header, and safe body preview).
176
- - **MR_MAGIC_ALLOWED_HOSTS** — comma-separated list of extra hostnames to allow
177
- when the MCP HTTP server binds to `0.0.0.0` (required by the MCP SDK for DNS
178
- rebinding protection). On Render, `RENDER_EXTERNAL_HOSTNAME` is automatically
179
- included; set `MR_MAGIC_ALLOWED_HOSTS` only when you have a custom domain or
180
- need to add additional hosts beyond `localhost`/`127.0.0.1`.
181
- - **MR_MAGIC_SDK_REPRO_HTTP_DEBUG** (default `0`) enables HTTP-level debugging
182
- output in `scripts/mcp-arg-boundary-sdk-repro.mjs` when validating argument
183
- boundary behavior from the SDK client path.
184
- - **MR_MAGIC_ROOT** overrides the project root used for loading `.env` and `.cache`.
185
- Useful when an MCP host launches the server from another directory.
186
- - **MR_MAGIC_ENV_PATH** lets you point to a specific `.env` file instead of the
187
- default `<project root>/.env`.
188
- - **AIRTABLE_PERSONAL_ACCESS_TOKEN** is required only when using the
189
- `push_catalog_to_airtable` tool. Generate a personal access token at
190
- https://airtable.com/create/tokens and grant it the `data.records:write` scope
191
- for the bases you want to write to.
192
- - For hosted deployments, inject the variables via your platform dashboard so
193
- no `.env` file is required at runtime.
194
-
195
- ### Getting the Musixmatch token
196
-
197
- All Musixmatch support in this project uses a captured browser session token.
198
- There is no OAuth callback — the fetch script captures and persists the token
199
- in one of two ways depending on your deployment:
200
-
201
- - **Cache token** (local dev): the fetch script writes the token to
202
- `.cache/musixmatch-token.json`. The server loads it on startup whenever a
203
- persistent, writable filesystem is available.
204
- - **Fallback token** (production/ephemeral): copy the captured token value and
205
- set it as `MUSIXMATCH_FALLBACK_TOKEN` (recommended) or `MUSIXMATCH_ALT_FALLBACK_TOKEN` in your
206
- platform's environment. This is the only reliable option on ephemeral hosts
207
- (Render free tier, containers without a mounted volume) where the filesystem
208
- is wiped between restarts.
209
-
210
- #### Workflow
211
-
212
- 1. From any machine that can open a browser, run:
88
+ Copy `.env.example` to `.env` (or inject via your platform dashboard). Variables are
89
+ grouped below by purpose.
213
90
 
214
- ```bash
215
- npm run fetch:musixmatch-token
216
- ```
91
+ ### Server and runtime
92
+
93
+ | Variable | Default | Description |
94
+ |---|---|---|
95
+ | `PORT` | `3444` / `3333` | Override server port. On Render this is set automatically (default `10000`). |
96
+ | `LOG_LEVEL` | `info` | Verbosity: `error` \| `warn` \| `info` \| `debug`. |
97
+ | `MR_MAGIC_QUIET_STDIO` | `0` | Set to `1` to suppress non-error stdout logs (forces `LOG_LEVEL=error`). Recommended under stdio MCP clients. |
98
+ | `MR_MAGIC_HTTP_TIMEOUT_MS` | `10000` | Global outbound HTTP timeout in milliseconds. |
99
+ | `MR_MAGIC_ROOT` | _(project root)_ | Override the project root used for `.env` and `.cache` path resolution. |
100
+ | `MR_MAGIC_ENV_PATH` | _(auto)_ | Point to a specific `.env` file instead of `<project root>/.env`. |
101
+ | `MR_MAGIC_ALLOWED_HOSTS` | _(empty)_ | Comma-separated extra hostnames allowed for DNS rebinding protection when binding to `0.0.0.0`. `RENDER_EXTERNAL_HOSTNAME` is included automatically on Render. Only needed for custom domains. |
102
+
103
+ ### Genius credentials
104
+
105
+ | Variable | Description |
106
+ |---|---|
107
+ | `GENIUS_CLIENT_ID` | OAuth client ID for auto-refresh (recommended). Get from [genius.com/api-clients](https://genius.com/api-clients). |
108
+ | `GENIUS_CLIENT_SECRET` | OAuth client secret for auto-refresh (recommended). |
109
+ | `GENIUS_ACCESS_TOKEN` | Static fallback bearer token. Used when client credentials are unavailable. |
110
+
111
+ Token resolution order (first match wins):
112
+
113
+ 1. In-memory runtime cache
114
+ 2. Auto-refresh via `GENIUS_CLIENT_ID` + `GENIUS_CLIENT_SECRET` ← **recommended**
115
+ 3. `GENIUS_ACCESS_TOKEN` env var (static, no auto-refresh)
116
+ 4. On-disk `.cache/genius-token.json` (local dev only)
117
+
118
+ ### Musixmatch credentials
119
+
120
+ | Variable | Description |
121
+ |---|---|
122
+ | `MUSIXMATCH_FALLBACK_TOKEN` | Token env var (1st priority). Use for production / ephemeral hosts. |
123
+ | `MUSIXMATCH_ALT_FALLBACK_TOKEN` | Token env var (2nd priority). Alternative name for the same token. |
124
+ | `MUSIXMATCH_TOKEN_CACHE` | Path to the on-disk cache file. Default: `.cache/musixmatch-token.json`. |
125
+ | `MUSIXMATCH_AUTO_FETCH` | Set to `1` to attempt headless token re-fetch when no token is found. |
126
+
127
+ ### Export and storage
128
+
129
+ | Variable | Default | Description |
130
+ |---|---|---|
131
+ | `MR_MAGIC_EXPORT_BACKEND` | `local` | Storage backend: `local` \| `inline` \| `redis`. |
132
+ | `MR_MAGIC_EXPORT_DIR` | `exports/` | Absolute path for local exports. Required when backend is `local`. |
133
+ | `MR_MAGIC_EXPORT_TTL_SECONDS` | `3600` | TTL for `local` and `redis` backends (ignored for `inline`). |
134
+ | `MR_MAGIC_DOWNLOAD_BASE_URL` | _(none)_ | Public base URL for download links, e.g. `https://lyrics.example.com`. |
135
+ | `MR_MAGIC_INLINE_PAYLOAD_MAX_CHARS` | `1500` | Character threshold at which `build_catalog_payload` auto-promotes to `reference` transport when `omitInlineLyrics` is `true`. |
136
+ | `UPSTASH_REDIS_REST_URL` | _(none)_ | Required when `MR_MAGIC_EXPORT_BACKEND=redis`. |
137
+ | `UPSTASH_REDIS_REST_TOKEN` | _(none)_ | Required when `MR_MAGIC_EXPORT_BACKEND=redis`. |
138
+
139
+ ### Airtable
140
+
141
+ | Variable | Description |
142
+ |---|---|
143
+ | `AIRTABLE_PERSONAL_ACCESS_TOKEN` | Required for `push_catalog_to_airtable`. Generate at [airtable.com/create/tokens](https://airtable.com/create/tokens) with `data.records:write` scope. |
144
+
145
+ ### Melon
146
+
147
+ | Variable | Description |
148
+ |---|---|
149
+ | `MELON_COOKIE` | Optional. Pin a session cookie for consistent results. Anonymous access generally works without it. |
150
+
151
+ ### Diagnostics and debugging
152
+
153
+ | Variable | Default | Description |
154
+ |---|---|---|
155
+ | `MR_MAGIC_MCP_HTTP_DIAGNOSTICS` | `0` | Set to `1` to log enriched request metadata at the Streamable HTTP transport boundary. |
156
+ | `MR_MAGIC_LOG_TOOL_ARGS_CHUNKS` | `0` | Set to `1` to emit chunk-by-chunk MCP tool argument previews for truncation debugging. |
157
+ | `MR_MAGIC_TOOL_ARG_CHUNK_SIZE` | `400` | Chunk size (chars) used when chunk logging is enabled. |
158
+ | `MR_MAGIC_SDK_REPRO_HTTP_DEBUG` | `0` | Set to `1` for verbose HTTP traces in the SDK repro harness script. |
159
+
160
+ ## Provider Credentials
161
+
162
+ ### Genius
163
+
164
+ Genius credentials are resolved in this order — the first available source wins:
165
+
166
+ 1. **Auto-refresh** (`GENIUS_CLIENT_ID` + `GENIUS_CLIENT_SECRET`) — the server calls
167
+ the Genius OAuth `client_credentials` endpoint at runtime and keeps the token
168
+ refreshed in memory. **Recommended for all deployments**, including Render and
169
+ ephemeral hosts. No disk, no scripts, no manual token copying.
217
170
 
218
- - Locally this pops up a Playwright-controlled Chromium window.
219
- - Remotely you can run the same command wherever you have GUI access (SSH + X11,
220
- VNC, RDP, etc.).
171
+ 2. **Fallback token** (`GENIUS_ACCESS_TOKEN`) a static bearer token. Works
172
+ everywhere but does not auto-refresh. Update by redeploying with a new value.
221
173
 
174
+ 3. **Cache token** (`.cache/genius-token.json`) — written by `npm run fetch:genius-token`.
175
+ Only suitable for local dev with a persistent filesystem.
176
+
177
+ ### Musixmatch
178
+
179
+ Musixmatch uses a captured browser session token. There is no OAuth callback.
180
+
181
+ **For production / ephemeral hosts (Render, containers):**
182
+
183
+ Set `MUSIXMATCH_FALLBACK_TOKEN` (first priority) or `MUSIXMATCH_ALT_FALLBACK_TOKEN`
184
+ (second priority) directly in your environment. These are the only reliable options
185
+ when the filesystem may be wiped between restarts.
186
+
187
+ **For local development:**
188
+
189
+ Run the fetch script once — it opens a Playwright-controlled Chromium window, signs
190
+ you in, and writes the token to `.cache/musixmatch-token.json`:
191
+
192
+ ```bash
193
+ npm run fetch:musixmatch-token
194
+ ```
195
+
196
+ The workflow:
197
+
198
+ 1. Run the script on any machine that can open a browser.
222
199
  2. Sign in with Musixmatch (Google sign-in works) when prompted.
200
+ 3. After the redirect to `https://www.musixmatch.com/discover`, the script prints
201
+ the captured token and writes the cache file.
202
+ 4. **For remote deployments:** copy the `token` value from the printed JSON and set
203
+ it as `MUSIXMATCH_FALLBACK_TOKEN` in your platform environment. Do **not** rely on
204
+ the cache file surviving restarts on ephemeral hosts.
205
+
206
+ > **Developer accounts:** Get API access from [developer.musixmatch.com](https://developer.musixmatch.com)
207
+ > and set the resulting token as `MUSIXMATCH_FALLBACK_TOKEN`.
208
+ >
209
+ > **Public accounts:** Visit [auth.musixmatch.com](https://auth.musixmatch.com), sign in,
210
+ > and capture the token using the script above.
211
+ >
212
+ > ⚠️ **WARNING:** Calling the API from an unauthorized account may result in a ban.
213
+
214
+ ### Melon
215
+
216
+ Fetching Melon endpoints works anonymously. If `MELON_COOKIE` is blank, the server
217
+ requests session cookies automatically. Set `MELON_COOKIE` to a complete cookie header
218
+ string only when you need pinned, reproducible sessions.
219
+
220
+ ## Export and Download Configuration
221
+
222
+ The `MR_MAGIC_EXPORT_BACKEND` variable controls where formatted lyrics are stored:
223
+
224
+ - **`local`** (default) — writes files to `MR_MAGIC_EXPORT_DIR` (or `exports/` when
225
+ unset). Make sure the target directory is writable. The `export_lyrics` tool also
226
+ returns the raw `content` field so clients can inline results when file writes fail.
227
+
228
+ - **`inline`** — skips disk writes entirely. Each export is returned in the tool
229
+ response with `content` populated and `skipped: true` to signal that persistence
230
+ was intentionally bypassed.
231
+
232
+ - **`redis`** — stores exports in Upstash. Requires `UPSTASH_REDIS_REST_URL`,
233
+ `UPSTASH_REDIS_REST_TOKEN`, and `MR_MAGIC_DOWNLOAD_BASE_URL`.
234
+
235
+ For Redis exports, `MR_MAGIC_DOWNLOAD_BASE_URL` must be the publicly reachable base URL
236
+ of your HTTP automation server (not the Upstash URL), e.g. `https://lyrics.example.com`.
237
+ Download links are built as `{base_url}/downloads/{id}/{ext}`.
238
+
239
+ For local testing:
240
+
241
+ ```bash
242
+ MR_MAGIC_DOWNLOAD_BASE_URL=http://127.0.0.1:3333
243
+ ```
244
+
245
+ > Even when using only the stdio MCP server, you still need the HTTP automation
246
+ > server running to serve `/downloads/:id/:ext` routes when the `redis` backend
247
+ > is enabled.
248
+
249
+ ## Local Deployment
250
+
251
+ Run whichever entrypoint you need via npm scripts:
252
+
253
+ ```bash
254
+ npm run server:http # JSON HTTP automation — 127.0.0.1:3333 by default
255
+ npm run server:mcp # MCP stdio transport — ideal for local MCP clients
256
+ npm run server:mcp:http # Streamable HTTP MCP — 127.0.0.1:3444 by default
257
+ npm run cli -- --help # CLI entrypoint
258
+ ```
259
+
260
+ Set provider tokens via `.env` before running. `dotenv` is for local convenience only —
261
+ production environments should inject vars directly.
262
+
263
+ ## Remote Deployment
223
264
 
224
- 3. After the redirect to `https://www.musixmatch.com/discover`, the script logs
225
- the captured token and writes the cache token file (default
226
- `<project>/.cache/musixmatch-token.json`). The file contains both the token
227
- value and the `web-desktop-app-v1.0` desktop cookie so the server can replay
228
- the session.
229
-
230
- 4. **For remote/ephemeral deployments:** copy the `token` value from the
231
- printed JSON and set it as `MUSIXMATCH_FALLBACK_TOKEN` in your platform
232
- environment (the fallback token). Do **not** rely on the cache file
233
- surviving a restart on ephemeral hosts.
234
- If `MUSIXMATCH_AUTO_FETCH=1`, the provider can attempt to re-run the fetch
235
- script headlessly when no token is found, but the initial sign-in still
236
- requires a browser.
237
-
238
- #### Developer Accounts
239
-
240
- 1. Get API access from `https://developer.musixmatch.com`
241
- 2. Run the script above and set the resulting token as `MUSIXMATCH_FALLBACK_TOKEN`
242
- (fallback token) in your environment, or keep the on-disk cache token in sync
243
- for local development.
244
-
245
- #### Public Account (WARNING: MAY RESULT IN BAN)
246
-
247
- 1. Visit `https://auth.musixmatch.com/`
248
- 2. Sign in with a Musixmatch account and allow the app. When redirected, the
249
- helper script above will capture the session and write the cache token.
250
- 3. Copy the `token` value and set it as `MUSIXMATCH_FALLBACK_TOKEN` for any remote
251
- environment that needs it.
252
-
253
- **WARNING: CALLING THE API FROM AN UNAUTHORIZED ACCOUNT MAY RESULT IN A BAN.**
254
-
255
- ### Optional Melon cookie
256
-
257
- Fetching Melon search/lyric endpoints still works with the MCP’s built-in cookie
258
- collection. If `MELON_COOKIE` is blank, the app will quietly request whatever
259
- session cookies the site provides, so you rarely need to copy a manual string.
260
- If you prefer to pin a cookie for repeatable results, set `MELON_COOKIE` to the
261
- complete cookie header you already trust.
262
-
263
- ### Export + download configuration
264
-
265
- - **Local files:** The default `local` backend writes into `exports/` (repo
266
- root). Override with `MR_MAGIC_EXPORT_DIR=/absolute/path` when the working
267
- directory isn’t writable. The `export_lyrics` tool also includes the raw
268
- `content` field so clients can still inline results if file writes fail.
269
- - **Redis downloads:** Set `MR_MAGIC_EXPORT_BACKEND=redis` plus
270
- `UPSTASH_REDIS_REST_URL`, `UPSTASH_REDIS_REST_TOKEN`, and
271
- `MR_MAGIC_DOWNLOAD_BASE_URL`. Each export is cached in Upstash for
272
- `MR_MAGIC_EXPORT_TTL_SECONDS` seconds, but the download link should point at
273
- _your own_ HTTP server’s `/downloads/:id/:ext` route (not the Upstash REST
274
- URL). In other words, `MR_MAGIC_DOWNLOAD_BASE_URL` must be the publicly
275
- reachable base URL for the HTTP automation server (e.g.,
276
- `https://lyrics.example.com`), and request paths are appended to it
277
- (`https://lyrics.example.com/downloads/...`). MCP clients can take the
278
- returned URLs and download the files from the same HTTP server or proxy where
279
- `/downloads` is routed.
280
- - Even if you’re only using the stdio MCP server locally, you still need the
281
- HTTP automation server running to serve those `/downloads/:id/:ext` routes
282
- whenever `redis` storage is enabled.
283
- - For local testing, set `MR_MAGIC_DOWNLOAD_BASE_URL=http://127.0.0.1:3333` (or
284
- `http://localhost:3333`) so the generated links look like
285
- `http://127.0.0.1:3333/downloads/<id>/<ext>`. In remote deployments, point it
286
- at your public host (e.g., `https://lyrics.example.com`). Only include a
287
- `:port` suffix when the HTTP server listens on a nonstandard port (e.g.,
288
- `https://lyrics.example.com:8443`). If you override the local port via `PORT`
289
- or CLI flags, update the base URL accordingly.
290
- - **Inline:** `MR_MAGIC_EXPORT_BACKEND=inline` is handy for sandboxes that
291
- prohibit writes. Instead of touching the file system or Redis, each export is
292
- returned inline in the tool/server response with `content` populated and
293
- `skipped: true` to signal that persistence was intentionally bypassed (not
294
- that the export failed).
295
-
296
- ## Local deployment
297
-
298
- Run whichever entrypoint you need via npm scripts so the repo’s `NODE_PATH`
299
- settings and dotenv loading are consistent:
300
-
301
- - `npm run server:http` — JSON HTTP automation endpoint (`127.0.0.1:3333` by
302
- default; honors `PORT`/CLI overrides).
303
- - `npm run server:mcp` — MCP stdio transport (ideal for local MCP clients that
304
- speak stdio).
305
- - `npm run server:mcp:http` — Streamable HTTP MCP transport
306
- (`127.0.0.1:3444` unless overridden).
307
- - `npm run cli` — interactive CLI entrypoint (`src/tools/cli.js`); combine with
308
- `server`, `search`, `find`, or `select` subcommands.
309
-
310
- Set provider tokens/env vars via `.env` before running any command.
311
- `dotenv` is only for local convenience—production runners should inject env vars
312
- directly.
313
-
314
- ## Remote deployment
315
-
316
- Ensure the deployment environment injects the same environment variables, then
317
- choose the transport you need. Typical remote workflows look like:
265
+ Install dependencies and start the desired transport:
318
266
 
319
267
  ```bash
320
268
  npm ci
321
- npm run server:http # or server:mcp / server:mcp:http
269
+ npm run server:mcp:http # or server:http / server:mcp
322
270
  ```
323
271
 
324
- Use a process manager (systemd, PM2, Docker CMD, etc.) to keep long-lived
325
- servers running.
272
+ Use a process manager (systemd, PM2, Docker `CMD`, etc.) to keep servers running.
326
273
 
327
274
  ### Deploying on Render
328
275
 
329
- The MCP HTTP server (`npm run server:mcp:http`) is ready to deploy on Render
330
- with no extra configuration. Render automatically sets two environment
331
- variables that the server reads at startup:
276
+ Both HTTP servers (`server:mcp:http` and `server:http`) are ready for Render with
277
+ no extra network configuration. Render automatically sets:
332
278
 
333
- | Variable | Set by Render | Value |
334
- |----------|--------------|-------|
335
- | `RENDER` | ✅ always | `"true"` |
336
- | `PORT` | ✅ always | `10000` (default, overridable in dashboard) |
279
+ | Variable | Value |
280
+ |---|---|
281
+ | `RENDER` | `"true"` |
282
+ | `PORT` | `10000` (default; overridable in the Render Dashboard) |
283
+ | `RENDER_EXTERNAL_HOSTNAME` | Your service hostname, e.g. `myapp.onrender.com` |
337
284
 
338
- When `RENDER=true` is detected, the server automatically binds to `0.0.0.0`
339
- (required by Render's load balancer) on the `PORT` Render assigns. You do
340
- **not** need to set `HOST`, `PORT`, or any other network variable in your
341
- Render service settings.
285
+ When `RENDER=true` is detected, the server binds to `0.0.0.0` automatically and reads
286
+ the platform-assigned `PORT`. No manual `HOST` or `PORT` configuration is needed.
287
+
288
+ The `RENDER_EXTERNAL_HOSTNAME` is automatically added to the DNS rebinding `allowedHosts`
289
+ list so the MCP SDK does not emit host-validation warnings.
342
290
 
343
291
  Recommended Render service settings:
344
292
 
345
293
  - **Start Command:** `npm run server:mcp:http`
346
- - **Environment:** inject your provider credentials (`GENIUS_CLIENT_ID`,
347
- `GENIUS_CLIENT_SECRET`, `MUSIXMATCH_FALLBACK_TOKEN`, etc.) via the Render
348
- Dashboard → Environment tab
294
+ - **Environment:** set provider credentials (`GENIUS_CLIENT_ID`, `GENIUS_CLIENT_SECRET`,
295
+ `MUSIXMATCH_FALLBACK_TOKEN`, etc.) in the Render Dashboard → Environment tab
349
296
  - **Health Check Path:** `/health` (returns `{ "status": "ok", "providers": [...] }`)
350
297
 
351
- > **Note:** Render's default `PORT` is `10000`. If you set a custom `PORT`
352
- > in the Render Dashboard the server will honour it; otherwise `10000` is used.
353
- > The old `3444` default is only active when running outside Render without a
354
- > `PORT` env var.
355
-
356
- > **Custom domains:** If you have a custom domain on Render (e.g.,
357
- > `mymcp.example.com`), add it to `MR_MAGIC_ALLOWED_HOSTS` in the Render
358
- > Dashboard → Environment tab so the SDK's DNS rebinding protection accepts
359
- > requests with that `Host` header.
360
-
361
- - **MCP server (Stdio)** for local Model Context Protocol clients (use the
362
- bundled CLI: `npm run server:mcp` or call `node ./src/bin/mcp-server.js`).
363
- - **MCP server (Streamable HTTP)** for remote MCP clients that speak the Streamable HTTP
364
- transport (`npm run server:mcp:http`). When running the Streamable HTTP transport
365
- in remote environments, restrict ingress (e.g., `0.0.0.0:3444` behind auth)
366
- and provide allowed host/origin headers via the MCP SDK options if needed.
367
- - **Standard JSON HTTP server** for container/remote automation (`npm run server:http`).
368
- - **CLI** for ad-hoc/manual usage (one-off SSH sessions, CI jobs, or ephemeral
369
- workers). Invoke with `npm run cli <subcommand>` or `npx mrmagic-cli <subcommand>`;
370
- it isn’t designed to run as a long-lived daemon because it exits
371
- after each command completes.
372
-
373
- In a local clone, prefer `npm run cli <subcommand>`. Use `mrmagic-cli ...`
374
- only after `npm link` or a global install places the binary on `PATH`.
375
-
376
- ## MCP tools
377
-
378
- Both STDIO and Streamable HTTP transports expose the same tool registry:
379
-
380
- | Tool name | Purpose |
381
- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
382
- | `find_lyrics` | Fetch best lyrics (prefers synced) plus metadata and payload. |
383
- | `build_catalog_payload` | Return a compact record (title/link/lyrics) for Airtable-style inserts (supports structured lyric payloads). |
384
- | `find_synced_lyrics` | Like `find_lyrics` but rejects plain-only results. |
385
- | `search_lyrics` | List candidate matches across providers without hydration. |
386
- | `search_provider` | Query a single provider (requires the `provider` flag). |
387
- | `get_provider_status` | Report readiness and notes for each provider. |
388
- | `export_lyrics` | Download + write plain/LRC/SRT/romanized files to disk. |
389
- | `format_lyrics` | Format lyrics in memory (optional romanization) for display. |
390
- | `select_match` | Pick a prior result by provider/index/synced flag. |
391
- | `runtime_status` | Snapshot provider readiness plus present env vars. |
392
- | `push_catalog_to_airtable` | Write catalog records directly to Airtable with server-side lyric resolution — lyrics never pass through LLM arguments. Requires `AIRTABLE_PERSONAL_ACCESS_TOKEN`. |
393
-
394
- ### Airtable integration (server-side lyrics)
395
-
396
- Mr. Magic ships a dedicated Airtable workflow that routes lyrics entirely
397
- server-side so long lyric text never passes through LLM tool-call arguments.
398
- This eliminates the JSON truncation and malformed-request errors that occur
399
- when multiline Korean / CJK lyrics are interpolated into automation payloads.
400
-
401
- #### How it works
298
+ > For custom domains, add them to `MR_MAGIC_ALLOWED_HOSTS` (comma-separated) in
299
+ > your Render environment so the DNS rebinding protection accepts requests with
300
+ > those `Host` headers.
301
+
302
+ ### Transport selection
303
+
304
+ | Transport | Command | Use case |
305
+ |---|---|---|
306
+ | MCP stdio | `npm run server:mcp` | Local MCP clients that speak stdio |
307
+ | MCP Streamable HTTP | `npm run server:mcp:http` | Remote MCP clients |
308
+ | JSON HTTP automation | `npm run server:http` | Container / remote automations |
309
+ | CLI | `npm run cli` | Ad-hoc / SSH / CI one-shot commands |
310
+
311
+ ## MCP Tools
312
+
313
+ Both the stdio and Streamable HTTP transports expose the same tool registry:
314
+
315
+ | Tool | Purpose |
316
+ |---|---|
317
+ | `find_lyrics` | Fetch best lyrics (prefers synced) plus metadata and payload. |
318
+ | `find_synced_lyrics` | Like `find_lyrics` but rejects plain-only results. |
319
+ | `search_lyrics` | List candidate matches across all providers without downloading lyrics. |
320
+ | `search_provider` | Query a single named provider. |
321
+ | `get_provider_status` | Report readiness and notes for each provider. |
322
+ | `format_lyrics` | Format lyrics in memory (optional romanization) for display. |
323
+ | `export_lyrics` | Write plain / LRC / SRT / romanized files to the export backend. |
324
+ | `select_match` | Pick a prior result by provider, index, or synced flag. |
325
+ | `build_catalog_payload` | Return a compact record (title / link / lyrics) for Airtable-style inserts. |
326
+ | `push_catalog_to_airtable` | Write catalog records to Airtable server-side — lyrics never pass through LLM arguments. Requires `AIRTABLE_PERSONAL_ACCESS_TOKEN`. |
327
+ | `runtime_status` | Snapshot provider readiness plus relevant env vars. |
328
+
329
+ ## Airtable Integration
330
+
331
+ Mr. Magic routes lyrics entirely server-side so long lyric text never passes through
332
+ LLM tool-call arguments. This eliminates the JSON truncation and malformed-request
333
+ errors that occur when multiline Korean / CJK lyrics are interpolated into payloads.
334
+
335
+ ### How it works
402
336
 
403
337
  1. **Call `build_catalog_payload`** for each song. The response contains a
404
- `lyricsCacheKey` a short slug like `kda-ill-show-you` that identifies the
405
- resolved lyrics in the server's in-memory LRU cache (20 entries, shared
406
- across the MCP session).
338
+ `lyricsCacheKey` (e.g. `kda-ill-show-you`) that identifies the resolved lyrics
339
+ in the server's in-memory LRU cache (20 entries, shared across the MCP session).
407
340
 
408
- 2. **Write non-lyric fields** (Song title, Spotify link, etc.) using your
409
- Airtable MCP's bulk create/update tools. The Airtable MCP can send up to 10
410
- records per call; use that fully. Capture the `recordId` returned for each
411
- created record.
341
+ 2. **Create Airtable records** (Song title, Spotify link, etc.) using your Airtable
342
+ MCP's bulk create tools (up to 10 records per call). Capture the `recordId`
343
+ returned for each created record.
412
344
 
413
- 3. **Call `push_catalog_to_airtable`** with the `recordId`, `lyricsFieldId`,
414
- and `lyricsCacheKey`. The server looks up the cached lyrics and calls the
415
- Airtable REST API directly. The lyric text **never leaves the server process**
416
- as an MCP argument.
345
+ 3. **Call `push_catalog_to_airtable`** with `recordId`, `lyricsFieldId`, and
346
+ `lyricsCacheKey`. The server looks up the cached lyrics and calls the Airtable
347
+ REST API directly. Lyric text **never leaves the server process** as an MCP argument.
417
348
 
418
- #### push_catalog_to_airtable call shape
349
+ ### `push_catalog_to_airtable` call shape
419
350
 
420
351
  ```json
421
352
  {
@@ -429,146 +360,90 @@ when multiline Korean / CJK lyrics are interpolated into automation payloads.
429
360
  }
430
361
  ```
431
362
 
432
- Pass `"splitLyricsUpdate": true` if the combined create + lyrics payload is too
433
- large — this forces a two-step create → PATCH so the Airtable API never sees
434
- the full payload in one request.
363
+ Set `"splitLyricsUpdate": true` when the combined create + lyrics payload is too large —
364
+ this forces a two-step create → PATCH so the full payload is never sent in one request.
435
365
 
436
- #### Bundled prompt template
366
+ ### Bundled prompt template
437
367
 
438
- `prompts/airtable-song-importer.md` (shipped in the npm package under
439
- `prompts/`) is a ready-to-use system prompt for MCP assistants that import
440
- songs into Airtable in bulk. It covers:
368
+ `prompts/airtable-song-importer.md` (shipped in the npm package) is a ready-to-use
369
+ system prompt for MCP assistants that bulk-import songs into Airtable. It covers:
441
370
 
442
- - Phased execution: resolve all data → bulk create (Song + Spotify link)
443
- write lyrics → SRT export
371
+ - Phased execution: resolve → bulk create write lyrics SRT export
444
372
  - Bulk record creation up to 10 records per Airtable MCP call
445
373
  - Spotify link resolution via the Spotify MCP
446
374
  - Romanized lyric priority for K-pop / CJK content
447
375
  - `splitLyricsUpdate` fallback for oversized payloads
448
- - SRT export delivery requirements
449
376
 
450
- Copy the contents of `prompts/airtable-song-importer.md` into your MCP
451
- client's system prompt to deploy this workflow immediately.
377
+ Copy the file contents into your MCP client's system prompt to deploy immediately.
452
378
 
453
- #### Safe lyric payload handoff (Airtable-friendly)
379
+ ### Safe lyric payload handoff
454
380
 
455
- The `build_catalog_payload` tool now exposes extra options that solve the
456
- "inline JSON lyric" problem reported in workflows that pipe lyrics straight
457
- into Airtable tool calls. Large multiline text that contains quotes, emoji, or
458
- Unicode can corrupt downstream JSON when it is interpolated directly into a
459
- payload string. To avoid this, request a structured lyric payload instead of
460
- embedding the raw text:
381
+ To avoid embedding raw lyric text in tool-call arguments, request a structured payload:
461
382
 
462
- ```jsonc
383
+ ```json
463
384
  {
464
385
  "track": { "artist": "K/DA", "title": "I'll Show You" },
465
386
  "options": {
466
387
  "omitInlineLyrics": true,
467
- "lyricsPayloadMode": "payload" // or "reference"
388
+ "lyricsPayloadMode": "payload"
468
389
  }
469
390
  }
470
391
  ```
471
392
 
472
- Important transport rule for MCP callers:
473
-
474
- - `tools/call.params.arguments` must be a JSON object/record.
475
- - Do **not** pre-serialize arguments into one giant JSON string for multiline
476
- lyrics.
477
- - `build_catalog_payload` and `select_match` now enforce object arguments and
478
- reject stringified payloads to prevent truncation-prone request patterns.
479
-
480
- - `omitInlineLyrics: true` removes the `lyrics`, `plainLyrics`, and
481
- `romanizedPlainLyrics` fields so the response stays compact and safe to log.
482
- - `lyricsPayloadMode: "payload"` adds `lyricsPayload` metadata and typically
483
- includes full text in a structured object (transport = `inline`). In compact
484
- Airtable-safe flows (`omitInlineLyrics: true`) the server may auto-promote
485
- transport to `reference` for long lyric payloads.
486
- - `lyricsPayloadMode: "reference"` stores the lyrics via the export storage
487
- backend (local/inline/redis) and returns a `lyricsPayload.reference` object
488
- containing the file path or download URL instead of the raw text.
489
- - `airtableSafePayload: true` additionally exposes `lyricsPayload.airtableEscapedContent`
490
- which is pre-escaped for JSON bodies (quotes/backslashes/newlines rendered as
491
- literal `\"`, `\\`, `\n`) and prefers compact/reference-style payload handoff
492
- when paired with `omitInlineLyrics: true`.
493
- - Optional `lyricsPayloadOutput` lets you override the output directory when
494
- using the default local backend.
495
-
496
- Downstream automations (Airtable, Zapier, Make, etc.) should map either
497
- `lyricsPayload.content` (inline mode) or fetch from
498
- `lyricsPayload.reference.url/filePath` (reference mode) and place that string
499
- into Airtable using the platform’s native variable substitution. This avoids
500
- hand-written JSON concatenation and eliminates the malformed request errors
501
- seen with long lyric fields.
502
-
503
- #### SDK vs fetch calling guidance for Airtable-safe payloads
504
-
505
- - **Preferred:** MCP SDK client calls (`client.callTool`) with `arguments` as a
506
- native object.
507
- - **Also valid:** raw `fetch`/HTTP requests, as long as you build one outer
508
- request object and call `JSON.stringify()` once at send time.
509
- - **Avoid:** manual JSON string templates that interpolate multiline lyrics.
510
-
511
- SDK example (recommended):
393
+ Option reference:
394
+
395
+ - `omitInlineLyrics: true` removes `lyrics`, `plainLyrics`, and `romanizedPlainLyrics`
396
+ from the response, keeping it compact.
397
+ - `lyricsPayloadMode: "payload"` — adds a `lyricsPayload` object with the full text
398
+ inline (`transport: "inline"`). May auto-promote to `"reference"` for long lyrics when
399
+ `omitInlineLyrics` is also `true`.
400
+ - `lyricsPayloadMode: "reference"` — stores lyrics via the export backend and returns
401
+ a `lyricsPayload.reference` object with `filePath` or `url` instead of raw text.
402
+ - `airtableSafePayload: true` adds `lyricsPayload.airtableEscapedContent` (quotes /
403
+ backslashes / newlines pre-escaped) and prefers compact / reference-style handoff.
404
+
405
+ **Important:** `tools/call.params.arguments` must be a plain JSON object. Do not
406
+ pre-serialize arguments into a string — `build_catalog_payload` and `select_match`
407
+ reject stringified payloads.
408
+
409
+ ### Calling patterns
410
+
411
+ Preferred (MCP SDK):
512
412
 
513
413
  ```js
514
414
  await client.callTool({
515
415
  name: 'build_catalog_payload',
516
416
  arguments: {
517
417
  track: { artist: 'K/DA', title: "I'll Show You" },
518
- options: {
519
- omitInlineLyrics: true,
520
- lyricsPayloadMode: 'payload',
521
- airtableSafePayload: true
522
- }
418
+ options: { omitInlineLyrics: true, lyricsPayloadMode: 'payload', airtableSafePayload: true }
523
419
  }
524
420
  });
525
421
  ```
526
422
 
527
- Fetch example (safe when still object-based):
423
+ Also valid (raw `fetch`, object-based):
528
424
 
529
425
  ```js
530
- const body = {
531
- jsonrpc: '2.0',
532
- id: 1,
533
- method: 'tools/call',
534
- params: {
535
- name: 'build_catalog_payload',
536
- arguments: {
537
- track: { artist: 'K/DA', title: "I'll Show You" },
538
- options: {
539
- omitInlineLyrics: true,
540
- lyricsPayloadMode: 'payload',
541
- airtableSafePayload: true
542
- }
543
- }
544
- }
545
- };
546
-
547
426
  await fetch('http://127.0.0.1:3444/mcp', {
548
427
  method: 'POST',
549
- headers: {
550
- 'Content-Type': 'application/json',
551
- Accept: 'application/json, text/event-stream'
552
- },
553
- body: JSON.stringify(body)
428
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/event-stream' },
429
+ body: JSON.stringify({
430
+ jsonrpc: '2.0', id: 1, method: 'tools/call',
431
+ params: {
432
+ name: 'build_catalog_payload',
433
+ arguments: {
434
+ track: { artist: 'K/DA', title: "I'll Show You" },
435
+ options: { omitInlineLyrics: true, lyricsPayloadMode: 'payload', airtableSafePayload: true }
436
+ }
437
+ }
438
+ })
554
439
  });
555
440
  ```
556
441
 
557
- #### Debugging malformed MCP tool arguments (truncation checks)
442
+ **Avoid:** manual JSON string templates that interpolate multiline lyrics.
558
443
 
559
- When an MCP client sends malformed JSON in `arguments` (for example, a lyric
560
- string that was truncated mid-quote), the server now validates and normalizes
561
- incoming arguments at the transport boundary before tool execution:
444
+ ### Debugging truncated arguments
562
445
 
563
- - Object args are passed through directly (preferred path).
564
- - For compatibility tools, string args are parsed once; parse failures return a
565
- consistent `Invalid JSON format for params: ...` error.
566
- - For Airtable-heavy tools (`build_catalog_payload`, `select_match`), string
567
- args are rejected so callers keep `params.arguments` as object/record.
568
- - Logs include argument length plus head/tail previews to pinpoint where data
569
- was cut off.
570
-
571
- For deeper diagnostics, enable chunk logging:
446
+ Enable chunk logging to diagnose malformed / truncated MCP tool arguments:
572
447
 
573
448
  ```bash
574
449
  MR_MAGIC_LOG_TOOL_ARGS_CHUNKS=1
@@ -576,63 +451,24 @@ MR_MAGIC_TOOL_ARG_CHUNK_SIZE=400
576
451
  LOG_LEVEL=debug
577
452
  ```
578
453
 
579
- This emits chunk-by-chunk previews so you can identify whether truncation
580
- occurred before or at MCP transport ingress.
581
-
582
- Recommended debugging presets:
583
-
584
- - **Normal operation (default):**
585
-
586
- ```env
587
- LOG_LEVEL=info
588
- MR_MAGIC_LOG_TOOL_ARGS_CHUNKS=0
589
- ```
590
-
591
- - **General verbose debugging (without chunk spam):**
592
-
593
- ```env
594
- LOG_LEVEL=debug
595
- MR_MAGIC_LOG_TOOL_ARGS_CHUNKS=0
596
- ```
454
+ Recommended presets:
597
455
 
598
- - **Truncation-focused diagnostics (large payload issues):**
456
+ | Scenario | `LOG_LEVEL` | `MR_MAGIC_LOG_TOOL_ARGS_CHUNKS` |
457
+ |---|---|---|
458
+ | Normal operation | `info` | `0` |
459
+ | General verbose | `debug` | `0` |
460
+ | Truncation diagnostics | `debug` | `1` |
599
461
 
600
- ```env
601
- LOG_LEVEL=debug
602
- MR_MAGIC_LOG_TOOL_ARGS_CHUNKS=1
603
- MR_MAGIC_TOOL_ARG_CHUNK_SIZE=400
604
- ```
462
+ ## MCP Client Configuration
605
463
 
606
- Use the truncation-focused preset only while investigating malformed JSON,
607
- then switch chunk logging back off to keep logs compact.
464
+ > ⚠️ **Stdio MCP clients:** Always invoke the server binary directly never via
465
+ > `npm run server:mcp`. The npm script preamble (`> mr-magic-mcp-server@x.x.x …`) is
466
+ > written to stdout before Node starts, and stdio MCP clients try to parse every stdout
467
+ > line as JSON-RPC, causing "Unexpected token '>'" errors on every connection.
608
468
 
609
- ### MCP client configuration
469
+ ### npx (recommended — no clone required)
610
470
 
611
- > ⚠️ **Important for stdio MCP clients:** Always invoke the server via the
612
- > binary directly rather than `npm run server:mcp`. When `npm` runs a script it
613
- > echoes a preamble like `> mr-magic-mcp-server@x.x.x server:mcp` to stdout
614
- > before the Node process starts. Cline and other stdio MCP clients try to parse
615
- > every stdout line as JSON-RPC, so those `>` lines cause "Unexpected token '>'"
616
- > parse errors on every connection attempt. Direct invocation produces no such
617
- > preamble.
618
-
619
- #### npx (recommended — no clone needed)
620
-
621
- Works with any MCP client that supports `command`/`args`. The package is fetched
622
- from npm on first run and cached locally:
623
-
624
- ```json
625
- {
626
- "mcpServers": {
627
- "Mr. Magic": {
628
- "command": "npx",
629
- "args": ["-y", "mr-magic-mcp-server"]
630
- }
631
- }
632
- }
633
- ```
634
-
635
- Add env vars inline if your client supports the `env` field:
471
+ Works with any MCP client that supports `command` / `args`:
636
472
 
637
473
  ```json
638
474
  {
@@ -650,10 +486,9 @@ Add env vars inline if your client supports the `env` field:
650
486
  }
651
487
  ```
652
488
 
653
- #### Global install
489
+ ### Global install
654
490
 
655
- After `npm install -g mr-magic-mcp-server`, invoke the `mcp-server` binary
656
- directly (it's on `PATH`):
491
+ After `npm install -g mr-magic-mcp-server`, the `mcp-server` binary is on `PATH`:
657
492
 
658
493
  ```json
659
494
  {
@@ -665,10 +500,9 @@ directly (it's on `PATH`):
665
500
  }
666
501
  ```
667
502
 
668
- #### Local repo — Cline config
503
+ ### Local repo — Cline
669
504
 
670
- Cline supports `cwd`, so you can call `node` directly — **do not use `npm run`
671
- here**, as npm's script echo will corrupt the stdio stream:
505
+ Cline supports `cwd`, so you can invoke `node` directly:
672
506
 
673
507
  ```json
674
508
  {
@@ -685,10 +519,9 @@ here**, as npm's script echo will corrupt the stdio stream:
685
519
  }
686
520
  ```
687
521
 
688
- #### Local repo — Standard config (no cwd support)
522
+ ### Local repo — clients without `cwd` support
689
523
 
690
- For clients like TypingMind that don't support a `cwd` field, use a shell
691
- wrapper with the absolute path:
524
+ For clients like TypingMind that don't support `cwd`, use a shell wrapper:
692
525
 
693
526
  ```json
694
527
  {
@@ -701,113 +534,125 @@ wrapper with the absolute path:
701
534
  }
702
535
  ```
703
536
 
704
- ### Manual Testing
537
+ ## CLI
538
+
539
+ A single CLI entrypoint (`mrmagic-cli`) is published with the package. Inside the
540
+ local repo use `npm run cli -- <subcommand>` unless you have run `npm link` or
541
+ installed globally.
542
+
543
+ ### Commands
705
544
 
706
- This section documents **manual, copy/paste HTTP tests** for both transports:
545
+ | Command | Purpose | Notable flags |
546
+ |---|---|---|
547
+ | `mrmagic-cli search` | List candidates across providers without downloading. | `--artist`, `--title`, `--provider`, `--duration`, `--show-all`, `--pick` |
548
+ | `mrmagic-cli find` | Resolve best lyric (prefers synced) and print / export. | `--providers`, `--synced-only`, `--export`, `--format`, `--output`, `--no-romanize`, `--choose`, `--index` |
549
+ | `mrmagic-cli select` | Pick first match from a prioritized provider list. | `--providers`, `--artist`, `--title`, `--require-synced` |
550
+ | `mrmagic-cli server` | Start the JSON automation API. | `--host`, `--port`, `--remote` |
551
+ | `mrmagic-cli server:mcp` | Start the MCP stdio server. | — |
552
+ | `mrmagic-cli server:mcp:http` | Start the Streamable HTTP MCP server. | `--host`, `--port`, `--remote`, `--sessionless` |
553
+ | `mrmagic-cli search-provider` | Query a single provider only. | `--provider`, `--artist`, `--title` |
554
+ | `mrmagic-cli status` | Print provider readiness. | — |
555
+
556
+ ### Examples
557
+
558
+ ```bash
559
+ # Search all providers
560
+ npm run cli -- search --artist "BLACKPINK" --title "Kill This Love"
707
561
 
708
- - JSON automation API (`npm run server:http`, default `http://127.0.0.1:3333`)
709
- - MCP Streamable HTTP API (`npm run server:mcp:http`, default `http://127.0.0.1:3444/mcp`)
562
+ # Find best lyric (prefers synced LRC)
563
+ npm run cli -- find --artist "Nayeon" --title "POP!"
710
564
 
711
- If you want automated checks too, keep these handy:
565
+ # Pick first synced match from a prioritized provider list
566
+ npm run cli -- select --providers lrclib,genius --artist "Nayeon" --title "POP!" --require-synced
712
567
 
713
- - `npm run test` full bundled test runner (`tests/run-tests.js`)
714
- - `node tests/mcp-tools.test.js` – raw MCP integration harness
715
- - `npm run repro:mcp:arg-boundary` – direct JSON-RPC repro harness for
716
- object-vs-string argument boundary checks.
717
- - `npm run repro:mcp:arg-boundary:sdk` – SDK client transport repro harness
718
- (supports `MR_MAGIC_SDK_REPRO_HTTP_DEBUG=1` for verbose HTTP traces).
719
- - `npm run lint` – ESLint
720
- - `npm run format:check` – Prettier check mode
568
+ # Start JSON automation API on a custom port
569
+ mrmagic-cli server --port 4000
570
+ ```
721
571
 
722
- #### 1) Manual JSON HTTP testing (`server:http`)
572
+ ### npm argument forwarding
723
573
 
724
- Start the server in one terminal:
574
+ Both of these forms work:
725
575
 
726
576
  ```bash
727
- npm run server:http
577
+ npm run cli search --artist "K/DA" --title "I'll Show You"
578
+ npm run cli -- search --artist "K/DA" --title "I'll Show You"
728
579
  ```
729
580
 
730
- The JSON API accepts:
581
+ For direct binary usage: `mrmagic-cli search --artist "K/DA" --title "I'll Show You"`.
731
582
 
732
- - `GET /health`
733
- - `POST /` with body shape:
583
+ ## Manual Testing
734
584
 
735
- ```json
736
- {
737
- "action": "find | findSynced | search",
738
- "track": { "artist": "...", "title": "...", "album": "..." },
739
- "options": { "...": "..." }
740
- }
585
+ Automated checks:
586
+
587
+ ```bash
588
+ npm run test # full bundled test runner
589
+ node tests/mcp-tools.test.js # raw MCP integration harness
590
+ npm run repro:mcp:arg-boundary # JSON-RPC argument boundary repro
591
+ npm run repro:mcp:arg-boundary:sdk # SDK client transport repro
592
+ npm run lint
593
+ npm run format:check
594
+ ```
595
+
596
+ ### JSON HTTP server (`server:http`)
597
+
598
+ Start the server:
599
+
600
+ ```bash
601
+ npm run server:http
741
602
  ```
742
603
 
743
- ##### A. Health check
604
+ Default base URL: `http://127.0.0.1:3333`
605
+
606
+ Accepted requests:
607
+
608
+ - `GET /health`
609
+ - `POST /` with body `{ "action": "find|findSynced|search", "track": {...}, "options": {...} }`
610
+
611
+ #### Health check
744
612
 
745
613
  ```bash
746
614
  curl -sS http://127.0.0.1:3333/health | jq
747
615
  ```
748
616
 
749
- ##### B. Basic lyric lookup (`action=find`)
617
+ #### Basic lyric lookup (`action=find`)
750
618
 
751
619
  ```bash
752
620
  curl -sS -X POST http://127.0.0.1:3333 \
753
621
  -H 'Content-Type: application/json' \
754
- -d '{
755
- "action":"find",
756
- "track":{"artist":"Coldplay","title":"Yellow"},
757
- "options":{}
758
- }' | jq
622
+ -d '{"action":"find","track":{"artist":"Coldplay","title":"Yellow"},"options":{}}' | jq
759
623
  ```
760
624
 
761
- ##### C. Synced-only lookup (`action=findSynced`)
625
+ #### Synced-only lookup (`action=findSynced`)
762
626
 
763
627
  ```bash
764
628
  curl -sS -X POST http://127.0.0.1:3333 \
765
629
  -H 'Content-Type: application/json' \
766
- -d '{
767
- "action":"findSynced",
768
- "track":{"artist":"Coldplay","title":"Yellow"},
769
- "options":{}
770
- }' | jq
630
+ -d '{"action":"findSynced","track":{"artist":"Coldplay","title":"Yellow"},"options":{}}' | jq
771
631
  ```
772
632
 
773
- ##### D. Search-only candidates (`action=search`)
633
+ #### Search candidates (`action=search`)
774
634
 
775
635
  ```bash
776
636
  curl -sS -X POST http://127.0.0.1:3333 \
777
637
  -H 'Content-Type: application/json' \
778
- -d '{
779
- "action":"search",
780
- "track":{"artist":"Coldplay","title":"Yellow"}
781
- }' | jq
638
+ -d '{"action":"search","track":{"artist":"Coldplay","title":"Yellow"}}' | jq
782
639
  ```
783
640
 
784
- ##### E. Export flow test (what we used for Redis/manual verification)
641
+ #### Export flow
785
642
 
786
643
  ```bash
787
644
  curl -sS -X POST http://127.0.0.1:3333 \
788
645
  -H 'Content-Type: application/json' \
789
- -d '{
790
- "action":"find",
791
- "track":{"artist":"Coldplay","title":"Yellow"},
792
- "options":{"export":true,"formats":["plain"]}
793
- }' | jq
646
+ -d '{"action":"find","track":{"artist":"Coldplay","title":"Yellow"},"options":{"export":true,"formats":["plain"]}}' | jq
794
647
  ```
795
648
 
796
649
  Look for `exports.plain` in the response:
797
650
 
798
- - **redis backend:** `url` should be `/downloads/<id>/txt`, `skipped: false`
799
- - **local backend:** `filePath` should be present
800
- - **inline backend:** `content` should be present and `skipped: true`
801
-
802
- ##### F. Verify the exported download URL
803
-
804
- If using Redis + HTTP downloads, fetch the URL returned from `exports.*.url`:
805
-
806
- ```bash
807
- curl -sS 'http://127.0.0.1:3333/downloads/<export-id>/txt' | head -n 10
808
- ```
651
+ - **redis backend:** `url` field present, `skipped: false`
652
+ - **local backend:** `filePath` field present
653
+ - **inline backend:** `content` field present, `skipped: true`
809
654
 
810
- If you prefer one-liner extraction with `jq`:
655
+ To fetch a Redis export download:
811
656
 
812
657
  ```bash
813
658
  EXPORT_URL=$(curl -sS -X POST http://127.0.0.1:3333 \
@@ -818,42 +663,33 @@ EXPORT_URL=$(curl -sS -X POST http://127.0.0.1:3333 \
818
663
  curl -sS "$EXPORT_URL" | head -n 10
819
664
  ```
820
665
 
821
- > Note: for Redis exports, ensure `MR_MAGIC_EXPORT_BACKEND=redis`, Upstash creds are set,
822
- > and `MR_MAGIC_DOWNLOAD_BASE_URL` matches the HTTP server base URL.
823
-
824
- #### 2) Manual MCP Streamable HTTP testing (`server:mcp:http`)
666
+ ### MCP Streamable HTTP server (`server:mcp:http`)
825
667
 
826
- Start MCP HTTP server in one terminal:
668
+ Start the server:
827
669
 
828
670
  ```bash
829
671
  npm run server:mcp:http
830
672
  ```
831
673
 
832
- Default endpoins:
833
- - `http://127.0.0.1:3444/mcp`
834
-
835
- All manual calls are JSON-RPC 2.0 requests.
674
+ Default endpoint: `http://127.0.0.1:3444/mcp`
836
675
 
837
- The MCP API also accepts:
676
+ All requests are JSON-RPC 2.0. Required headers:
838
677
 
839
- - `GET /health`
840
- - `POST /` with body shape:
841
-
842
- ```json
843
- {
844
- "action": "find | findSynced | search",
845
- "track": { "artist": "...", "title": "...", "album": "..." },
846
- "options": { "...": "..." }
847
- }
678
+ ```
679
+ Content-Type: application/json
680
+ Accept: application/json, text/event-stream
848
681
  ```
849
682
 
850
- ##### A. Health check
683
+ > Tip: use `--sessionless` for easier stateless manual testing:
684
+ > `npm run cli -- server:mcp:http --sessionless`
685
+
686
+ #### Health check
851
687
 
852
688
  ```bash
853
- curl -sS http://127.0.0.1:3333/health | jq
689
+ curl -sS http://127.0.0.1:3444/health | jq
854
690
  ```
855
691
 
856
- ##### B. List tools (`tools/list`)
692
+ #### List tools
857
693
 
858
694
  ```bash
859
695
  curl -sS -X POST http://127.0.0.1:3444/mcp \
@@ -862,269 +698,153 @@ curl -sS -X POST http://127.0.0.1:3444/mcp \
862
698
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq
863
699
  ```
864
700
 
865
- ##### C. Call `find_lyrics`
701
+ #### `find_lyrics`
866
702
 
867
703
  ```bash
868
704
  curl -sS -X POST http://127.0.0.1:3444/mcp \
869
705
  -H 'Content-Type: application/json' \
870
706
  -H 'Accept: application/json, text/event-stream' \
871
- -d '{
872
- "jsonrpc":"2.0",
873
- "id":2,
874
- "method":"tools/call",
875
- "params":{
876
- "name":"find_lyrics",
877
- "arguments":{"track":{"artist":"Coldplay","title":"Yellow"}}
878
- }
879
- }' | jq
707
+ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"find_lyrics","arguments":{"track":{"artist":"Coldplay","title":"Yellow"}}}}' | jq
880
708
  ```
881
709
 
882
- ##### D. Call `find_synced_lyrics`
710
+ #### `find_synced_lyrics`
883
711
 
884
712
  ```bash
885
713
  curl -sS -X POST http://127.0.0.1:3444/mcp \
886
714
  -H 'Content-Type: application/json' \
887
715
  -H 'Accept: application/json, text/event-stream' \
888
- -d '{
889
- "jsonrpc":"2.0",
890
- "id":3,
891
- "method":"tools/call",
892
- "params":{
893
- "name":"find_synced_lyrics",
894
- "arguments":{"track":{"artist":"Coldplay","title":"Yellow"}}
895
- }
896
- }' | jq
716
+ -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"find_synced_lyrics","arguments":{"track":{"artist":"Coldplay","title":"Yellow"}}}}' | jq
897
717
  ```
898
718
 
899
- ##### E. Call `build_catalog_payload` (default — inline lyrics)
719
+ #### `build_catalog_payload` — inline lyrics
900
720
 
901
721
  ```bash
902
722
  curl -sS -X POST http://127.0.0.1:3444/mcp \
903
723
  -H 'Content-Type: application/json' \
904
724
  -H 'Accept: application/json, text/event-stream' \
905
725
  -d '{
906
- "jsonrpc":"2.0",
907
- "id":4,
908
- "method":"tools/call",
909
- "params":{
910
- "name":"build_catalog_payload",
911
- "arguments":{
912
- "track":{"artist":"K/DA","title":"I'\''LL SHOW YOU"},
913
- "options":{"preferRomanized":false}
914
- }
915
- }
726
+ "jsonrpc":"2.0","id":4,"method":"tools/call",
727
+ "params":{"name":"build_catalog_payload","arguments":{"track":{"artist":"K/DA","title":"I'\''LL SHOW YOU"},"options":{"preferRomanized":false}}}
916
728
  }' | jq
917
729
  ```
918
730
 
919
- ##### F. Call `build_catalog_payload` (compact Airtable-safe mode)
731
+ #### `build_catalog_payload` Airtable-safe mode
920
732
 
921
733
  ```bash
922
734
  curl -sS -X POST http://127.0.0.1:3444/mcp \
923
735
  -H 'Content-Type: application/json' \
924
736
  -H 'Accept: application/json, text/event-stream' \
925
737
  -d '{
926
- "jsonrpc":"2.0",
927
- "id":5,
928
- "method":"tools/call",
929
- "params":{
930
- "name":"build_catalog_payload",
931
- "arguments":{
932
- "track":{"artist":"K/DA","title":"I'\''LL SHOW YOU"},
933
- "options":{
934
- "omitInlineLyrics":true,
935
- "lyricsPayloadMode":"payload",
936
- "airtableSafePayload":true
937
- }
938
- }
939
- }
738
+ "jsonrpc":"2.0","id":5,"method":"tools/call",
739
+ "params":{"name":"build_catalog_payload","arguments":{"track":{"artist":"K/DA","title":"I'\''LL SHOW YOU"},"options":{"omitInlineLyrics":true,"lyricsPayloadMode":"payload","airtableSafePayload":true}}}
940
740
  }' | jq
941
741
  ```
942
742
 
943
- ##### G. Call `search_lyrics` (all providers, no hydration)
743
+ #### `search_lyrics`
944
744
 
945
745
  ```bash
946
746
  curl -sS -X POST http://127.0.0.1:3444/mcp \
947
747
  -H 'Content-Type: application/json' \
948
748
  -H 'Accept: application/json, text/event-stream' \
949
- -d '{
950
- "jsonrpc":"2.0",
951
- "id":6,
952
- "method":"tools/call",
953
- "params":{
954
- "name":"search_lyrics",
955
- "arguments":{"track":{"artist":"Coldplay","title":"Yellow"}}
956
- }
957
- }' | jq
749
+ -d '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"search_lyrics","arguments":{"track":{"artist":"Coldplay","title":"Yellow"}}}}' | jq
958
750
  ```
959
751
 
960
- ##### H. Call `search_provider` (single provider)
752
+ #### `search_provider`
961
753
 
962
754
  ```bash
963
755
  curl -sS -X POST http://127.0.0.1:3444/mcp \
964
756
  -H 'Content-Type: application/json' \
965
757
  -H 'Accept: application/json, text/event-stream' \
966
- -d '{
967
- "jsonrpc":"2.0",
968
- "id":7,
969
- "method":"tools/call",
970
- "params":{
971
- "name":"search_provider",
972
- "arguments":{
973
- "provider":"lrclib",
974
- "track":{"artist":"Coldplay","title":"Yellow"}
975
- }
976
- }
977
- }' | jq
758
+ -d '{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"search_provider","arguments":{"provider":"lrclib","track":{"artist":"Coldplay","title":"Yellow"}}}}' | jq
978
759
  ```
979
760
 
980
- ##### I. Call `format_lyrics` (in-memory with romanization)
761
+ #### `format_lyrics`
981
762
 
982
763
  ```bash
983
764
  curl -sS -X POST http://127.0.0.1:3444/mcp \
984
765
  -H 'Content-Type: application/json' \
985
766
  -H 'Accept: application/json, text/event-stream' \
986
- -d '{
987
- "jsonrpc":"2.0",
988
- "id":8,
989
- "method":"tools/call",
990
- "params":{
991
- "name":"format_lyrics",
992
- "arguments":{
993
- "track":{"artist":"aespa","title":"Supernova"},
994
- "options":{"includeSynced":true}
995
- }
996
- }
997
- }' | jq
767
+ -d '{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"format_lyrics","arguments":{"track":{"artist":"aespa","title":"Supernova"},"options":{"includeSynced":true}}}}' | jq
998
768
  ```
999
769
 
1000
- ##### J. Call `export_lyrics`
770
+ #### `export_lyrics`
1001
771
 
1002
772
  ```bash
1003
773
  curl -sS -X POST http://127.0.0.1:3444/mcp \
1004
774
  -H 'Content-Type: application/json' \
1005
775
  -H 'Accept: application/json, text/event-stream' \
1006
- -d '{
1007
- "jsonrpc":"2.0",
1008
- "id":9,
1009
- "method":"tools/call",
1010
- "params":{
1011
- "name":"export_lyrics",
1012
- "arguments":{
1013
- "track":{"artist":"Coldplay","title":"Yellow"},
1014
- "options":{"formats":["plain","lrc","srt"]}
1015
- }
1016
- }
1017
- }' | jq
776
+ -d '{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"export_lyrics","arguments":{"track":{"artist":"Coldplay","title":"Yellow"},"options":{"formats":["plain","lrc","srt"]}}}}' | jq
1018
777
  ```
1019
778
 
1020
- ##### K. Call `get_provider_status`
779
+ #### `get_provider_status`
1021
780
 
1022
781
  ```bash
1023
782
  curl -sS -X POST http://127.0.0.1:3444/mcp \
1024
783
  -H 'Content-Type: application/json' \
1025
784
  -H 'Accept: application/json, text/event-stream' \
1026
- -d '{
1027
- "jsonrpc":"2.0",
1028
- "id":10,
1029
- "method":"tools/call",
1030
- "params":{"name":"get_provider_status","arguments":{}}
1031
- }' | jq
785
+ -d '{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"get_provider_status","arguments":{}}}' | jq
1032
786
  ```
1033
787
 
1034
- ##### K2. Call `runtime_status`
788
+ #### `runtime_status`
1035
789
 
1036
790
  ```bash
1037
791
  curl -sS -X POST http://127.0.0.1:3444/mcp \
1038
792
  -H 'Content-Type: application/json' \
1039
793
  -H 'Accept: application/json, text/event-stream' \
1040
- -d '{
1041
- "jsonrpc":"2.0",
1042
- "id":11,
1043
- "method":"tools/call",
1044
- "params":{"name":"runtime_status","arguments":{}}
1045
- }' | jq
794
+ -d '{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"runtime_status","arguments":{}}}' | jq
1046
795
  ```
1047
796
 
1048
- ##### M. Call `push_catalog_to_airtable` (server-side Airtable lyrics write)
797
+ #### `push_catalog_to_airtable`
1049
798
 
1050
- First call `build_catalog_payload` (section D or E above) to populate the
1051
- lyric cache and capture the returned `lyricsCacheKey`. Then use that key here
1052
- to write lyrics to Airtable entirely server-side — no lyric text passes through
1053
- your tool-call arguments.
799
+ First call `build_catalog_payload` to populate the lyric cache and capture `lyricsCacheKey`,
800
+ then pass it here no lyric text goes through tool-call arguments:
1054
801
 
1055
802
  ```bash
1056
803
  curl -sS -X POST http://127.0.0.1:3444/mcp \
1057
804
  -H 'Content-Type: application/json' \
1058
805
  -H 'Accept: application/json, text/event-stream' \
1059
806
  -d '{
1060
- "jsonrpc":"2.0",
1061
- "id":12,
1062
- "method":"tools/call",
1063
- "params":{
1064
- "name":"push_catalog_to_airtable",
1065
- "arguments":{
1066
- "baseId":"appeBUkVEp3N4RT0C",
1067
- "tableId":"tbl0y5XHFXpjUJXHu",
1068
- "recordId":"rec1234567890abcd",
1069
- "fields":{},
1070
- "lyricsFieldId":"fldHV1qmPYmsvglff",
1071
- "lyricsCacheKey":"kda-ill-show-you",
1072
- "preferRomanized":true
1073
- }
1074
- }
807
+ "jsonrpc":"2.0","id":12,"method":"tools/call",
808
+ "params":{"name":"push_catalog_to_airtable","arguments":{
809
+ "baseId":"appeBUkVEp3N4RT0C",
810
+ "tableId":"tbl0y5XHFXpjUJXHu",
811
+ "recordId":"rec1234567890abcd",
812
+ "fields":{},
813
+ "lyricsFieldId":"fldHV1qmPYmsvglff",
814
+ "lyricsCacheKey":"kda-ill-show-you",
815
+ "preferRomanized":true
816
+ }}
1075
817
  }' | jq
1076
818
  ```
1077
819
 
1078
- > Replace `baseId`, `tableId`, `recordId`, `lyricsFieldId`, and `lyricsCacheKey`
1079
- > with real values from your Airtable base and the `build_catalog_payload`
1080
- > response. Requires `AIRTABLE_PERSONAL_ACCESS_TOKEN` to be set in `.env`.
820
+ > Replace `baseId`, `tableId`, `recordId`, `lyricsFieldId`, and `lyricsCacheKey` with
821
+ > real values. Requires `AIRTABLE_PERSONAL_ACCESS_TOKEN`.
1081
822
 
1082
- ##### N. Call `select_match` (pick from a prior search result)
1083
-
1084
- First run `search_lyrics` (section F above) and capture the matches, then
1085
- pick the first synced result:
823
+ #### `select_match`
1086
824
 
1087
825
  ```bash
1088
826
  curl -sS -X POST http://127.0.0.1:3444/mcp \
1089
827
  -H 'Content-Type: application/json' \
1090
828
  -H 'Accept: application/json, text/event-stream' \
1091
829
  -d '{
1092
- "jsonrpc":"2.0",
1093
- "id":12,
1094
- "method":"tools/call",
1095
- "params":{
1096
- "name":"select_match",
1097
- "arguments":{
1098
- "matches": [
1099
- {
1100
- "provider":"lrclib",
1101
- "result":{
1102
- "title":"Yellow","artist":"Coldplay",
1103
- "synced":true,"plainOnly":false
1104
- }
1105
- }
1106
- ],
1107
- "criteria":{"requireSynced":true,"index":0}
1108
- }
1109
- }
830
+ "jsonrpc":"2.0","id":13,"method":"tools/call",
831
+ "params":{"name":"select_match","arguments":{
832
+ "matches":[{"provider":"lrclib","result":{"title":"Yellow","artist":"Coldplay","synced":true,"plainOnly":false}}],
833
+ "criteria":{"requireSynced":true,"index":0}
834
+ }}
1110
835
  }' | jq
1111
836
  ```
1112
837
 
1113
838
  > **MCP tool response shape:**
1114
- >
1115
- > - `result.structuredContent` — machine-friendly object (all fields present, full values)
839
+ > - `result.structuredContent` — machine-friendly object (all fields, full values)
1116
840
  > - `result.content[0].text` — complete pretty-printed JSON (identical to `structuredContent`)
1117
841
  >
1118
- > Both channels carry the same complete payload. There is no truncated preview
1119
- > block. Programmatic consumers should prefer `structuredContent`; LLM agents
1120
- > reading `content[0].text` get the full JSON string.
1121
-
1122
- > Tip: if your manual client has trouble with MCP sessions, start with
1123
- > `npm run cli -- server:mcp:http --sessionless` for easier stateless testing.
842
+ > Both channels carry the same complete payload. Programmatic consumers should prefer
843
+ > `structuredContent`; LLM agents reading `content[0].text` get the full JSON string.
1124
844
 
1125
- #### 3) Running both servers side-by-side
845
+ ### Running both servers side-by-side
1126
846
 
1127
- For export scenarios with Redis-backed downloads, run both servers in separate terminals:
847
+ For Redis-backed download scenarios, run both servers in separate terminals:
1128
848
 
1129
849
  ```bash
1130
850
  # Terminal 1
@@ -1134,78 +854,28 @@ npm run server:http
1134
854
  npm run server:mcp:http
1135
855
  ```
1136
856
 
1137
- Stop servers with `Ctrl+C` when done.
1138
-
1139
- ## CLI overview
1140
-
1141
- A single CLI entrypoint (`mrmagic-cli`) is published with the package.
1142
- Inside this repository, use `npm run cli -- --help` unless you've run
1143
- `npm link` (or installed globally) so `mrmagic-cli` is available on `PATH`.
1144
- Running `mrmagic-cli --help` (or `npm run cli -- --help` inside the repo),
1145
- prints a top-level summary, while subcommand-specific help—e.g.,
1146
- `mrmagic-cli search --help`—lists all flags
1147
- with descriptions, defaults, and examples.
1148
-
1149
- ### Command summary
1150
-
1151
- | Command | Purpose | Notable flags |
1152
- | ----------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1153
- | `mrmagic-cli search` | List candidate matches across providers without downloading lyrics. | `--artist`/`--title` (required track metadata), `--provider` (limit providers), `--duration` (match duration in ms), `--show-all` (print table), `--pick` (auto-select provider result). |
1154
- | `mrmagic-cli find` | Resolve the best lyric (prefers synced) and print/export it. | `--providers` (CSV priority list), `--synced-only` (reject plain results), `--export` (write files), `--format` (repeatable; e.g., lrc,srt), `--output` (custom export dir), `--no-romanize`, `--choose`/`--index` (select specific match). |
1155
- | `mrmagic-cli select` | Pick the first match from a prioritized provider list. | `--providers` (CSV order), `--artist`, `--title`, `--require-synced` (only accept synced lyrics). |
1156
- | `mrmagic-cli server` | Run the JSON automation API (same as `npm run server:http`). | `--host` (interface to bind; default 127.0.0.1), `--port` (listening port; overrides env/`PORT`), `--remote` (shorthand for `--host 0.0.0.0`). |
1157
- | `mrmagic-cli server:mcp` | Start the MCP stdio server (stdio transport). | _(none)_ |
1158
- | `mrmagic-cli server:mcp:http` | Start the Streamable HTTP MCP server. | `--host`, `--port`, `--remote`, `--sessionless` (disable per-session connection IDs; useful for stateless/manual debugging). |
1159
- | `mrmagic-cli search-provider` | Query a single provider only. | `--provider` (required provider name), `--artist`, `--title`. |
1160
- | `mrmagic-cli status` | Print provider readiness information. | _(none)_ |
1161
-
1162
- ### Command Examples
1163
-
1164
- - Local repo usage: `npm run cli -- search --artist "BLACKPINK" --title "Kill This Love"`
1165
- – list candidates across all providers.
1166
- - Local repo usage: `npm run cli -- find --artist "Nayeon" --title "POP!"`
1167
- – download the best lyric (prefers synced LRC when possible).
1168
- - Local repo usage: `npm run cli -- select --providers lrclib,genius --artist "Nayeon" --title "POP!" --require-synced`
1169
- – pick the first synced match from the prioritized provider list.
1170
- - Linked/global usage: `mrmagic-cli server --port 4000`
1171
- – run the JSON automation API locally once `mrmagic-cli` is on `PATH`.
1172
-
1173
- ### CLI troubleshooting (npm argument forwarding)
1174
-
1175
- The `cli` npm script supports both invocation styles:
1176
-
1177
- - ✅ `npm run cli search --artist "K/DA" --title "I'll Show You"`
1178
- - ✅ `npm run cli -- search --artist "K/DA" --title "I'll Show You"`
1179
-
1180
- The first form is preferred for readability, but both are supported.
1181
-
1182
- For direct binary usage, use `mrmagic-cli search --artist ... --title ...`.
1183
-
1184
- ## Provider notes
857
+ ## Provider Notes
1185
858
 
1186
- - **LRCLIB**: Public API with synced lyric coverage; no auth required.
1187
- - **Genius**: Requires credentials either `GENIUS_CLIENT_ID` + `GENIUS_CLIENT_SECRET`
1188
- for auto-refresh (recommended) or `GENIUS_ACCESS_TOKEN` as a static fallback token.
1189
- - **Musixmatch**: Requires a token either a **fallback token** set via
1190
- `MUSIXMATCH_FALLBACK_TOKEN` (recommended for production) or `MUSIXMATCH_ALT_FALLBACK_TOKEN` env var,
1191
- or a **cache token** written to disk by `npm run fetch:musixmatch-token` (local dev).
1192
- See "Getting the Musixmatch token" above for the full workflow.
1193
- - **Melon**: Works anonymously but benefits from `MELON_COOKIE` for reliability
1194
- if needed.
859
+ - **LRCLIB** Public API with synced lyric coverage. No auth required.
860
+ - **Genius**Requires `GENIUS_CLIENT_ID` + `GENIUS_CLIENT_SECRET` (auto-refresh,
861
+ recommended) or `GENIUS_ACCESS_TOKEN` (static fallback token).
862
+ - **Musixmatch** Requires a token. Use `MUSIXMATCH_FALLBACK_TOKEN` for production /
863
+ ephemeral hosts; use the on-disk cache token (`npm run fetch:musixmatch-token`) for
864
+ local dev. See [Musixmatch](#musixmatch) for the full workflow.
865
+ - **Melon** Works anonymously. Set `MELON_COOKIE` for pinned / reproducible sessions.
1195
866
 
1196
- Providers are queried concurrently, and results are normalized into a shared
1197
- schema exposed via the CLI, HTTP API, and MCP tools.
867
+ Providers are queried concurrently and results are normalized into a shared schema
868
+ exposed via the CLI, HTTP API, and MCP tools.
1198
869
 
1199
870
  ## Changelog
1200
871
 
1201
- See `CHANGELOG.md` for a summary of recent updates, including MCP transport
1202
- changes and test improvements.
872
+ See [`CHANGELOG.md`](CHANGELOG.md) for a full history of changes.
1203
873
 
1204
- ## LICENSE
874
+ ## License
1205
875
 
1206
- [MIT LICENSE](/LICENSE)
876
+ [MIT](LICENSE)
1207
877
 
1208
- I am not and cannot be held liable for any infrigement or ban from services
878
+ I am not and cannot be held liable for any infringement or ban from services
1209
879
  that could occur as a result of using this software. Your usage is solely
1210
880
  your responsibility. Godspeed.
1211
881