Mr. Magic MCP Server
by mrnajiboy
README.md
# Mr. Magic MCP Server
[](https://www.npmjs.com/package/mr-magic-mcp-server)
[](https://www.npmjs.com/package/mr-magic-mcp-server)
[](https://socket.dev/npm/package/mr-magic-mcp-server)
Mr. Magic bridges LRCLIB, Genius, Musixmatch, and Melon so MCP clients, JSON HTTP
automations, and CLI aficionados can all request lyrics from a single toolchain.
## Table of Contents
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Environment Variables](#environment-variables)
- [Provider Credentials](#provider-credentials)
- [Export and Download Configuration](#export-and-download-configuration)
- [Local Deployment](#local-deployment)
- [Remote Deployment](#remote-deployment)
- [HTTP Endpoints](#http-endpoints)
- [MCP Tools](#mcp-tools)
- [Airtable Integration](#airtable-integration)
- [MCP Client Configuration](#mcp-client-configuration)
- [CLI](#cli)
- [Manual Testing](#manual-testing)
- [Provider Notes](#provider-notes)
- [Changelog](#changelog)
- [License](#license)
## Prerequisites
- Node.js 18.17 or newer
- npm 9+
- macOS / Linux / WSL
- Provider credentials (see [Provider Credentials](#provider-credentials))
## Installation
Mr. Magic publishes four named binaries through npm:
| Binary | Transport | Default port |
| ----------------- | -------------------------------------- | ------------ |
| `mcp-server` | MCP stdio | n/a (stdio) |
| `mcp-http-server` | MCP Streamable HTTP & SSE + legacy SSE | `3444` |
| `http-server` | JSON HTTP automation | `3333` |
| `mrmagic-cli` | CLI | n/a |
Choose the option that matches how you'll use Mr. Magic.
---
### Option 1 — Local clone (recommended for persistent use)
Cloning the repo is the **simplest setup for local MCP clients** (Cline, Claude Desktop, etc.)
and for anyone who wants to use all features without extra ceremony.
Credentials live in a local `.env` file — no need to inject every variable through an MCP client
config. Token fetch scripts, the Playwright workflow, and local export storage all work out of the box.
1. Clone and install:
```bash
git clone https://github.com/mrnajiboy/mr-magic-mcp-server.git
cd mr-magic-mcp-server
npm install
```
2. Create a `.env` file (copy `.env.example` as a starting point) and fill in your credentials.
See [Environment Variables](#environment-variables) for the full list.
3. Run the desired entrypoint:
| What you want | Command |
| ---------------------------------- | --------------------------------- |
| MCP stdio (local MCP clients) | `node src/bin/mcp-server.js` |
| MCP Streamable HTTP & SSE (remote) | `node src/bin/mcp-http-server.js` |
| JSON HTTP automation | `node src/bin/http-server.js` |
| CLI | `node src/bin/cli.js --help` |
Or use the npm scripts:
```bash
npm run server:mcp # MCP stdio
npm run server:mcp:http # MCP Streamable HTTP & SSE — 127.0.0.1:3444
npm run server:http # JSON HTTP — 127.0.0.1:3333
npm run cli -- --help # CLI
```
> ⚠️ **Stdio MCP clients:** Do not use `npm run server:mcp` in your MCP client config.
> Use `node src/bin/mcp-server.js` directly. The npm script preamble is written to stdout
> before Node starts and causes `"Unexpected token '>'"` JSON-RPC errors on every connection.
See [MCP Client Configuration → Local repo](#local-repo--cline) for the client config snippets.
---
### Option 2 — npx (no clone, ephemeral or CI use)
`npx` is useful for quick one-off runs or CI contexts, but has an important limitation:
**the spawned process cannot read a local `.env` file** — every credential must be passed
as an environment variable in the shell or in your MCP client's `env` block.
This package publishes **multiple binaries**, so you must always specify which one you want
using `--package` and then the binary name. `npx -y mr-magic-mcp-server` is **not valid**
for this package — it would error because no binary named `mr-magic-mcp-server` exists.
#### MCP stdio server (for MCP client config)
```bash
# Shell (test only — real credentials come from MCP client env block)
MR_MAGIC_QUIET_STDIO=1 \
GENIUS_CLIENT_ID=your_id \
GENIUS_CLIENT_SECRET=your_secret \
MUSIXMATCH_DIRECT_TOKEN='...' \
npx -y --package mr-magic-mcp-server mcp-server
```
For MCP clients (Cline, Claude Desktop, etc.), put credentials in the `env` block of your
config — see [MCP Client Configuration → npx](#npx-no-clone-required).
#### MCP Streamable HTTP & SSE server (for remote / browser-based MCP clients)
```bash
# Streamable HTTP & SSE — listens on port 3444, endpoint: /mcp
GENIUS_CLIENT_ID=your_id \
GENIUS_CLIENT_SECRET=your_secret \
MUSIXMATCH_DIRECT_TOKEN='...' \
npx -y --package mr-magic-mcp-server mcp-http-server
```
Connect your client to `http://localhost:3444/mcp` (or your public URL + `/mcp`).
The same server exposes the **legacy SSE** endpoints for older clients:
- `GET /sse` — opens the event stream
- `POST /messages?sessionId=...` — sends JSON-RPC messages
Both protocols run on the same port simultaneously — no extra config needed.
#### JSON HTTP automation server
```bash
# JSON HTTP — listens on port 3333, endpoint: POST /
GENIUS_CLIENT_ID=your_id \
GENIUS_CLIENT_SECRET=your_secret \
MUSIXMATCH_DIRECT_TOKEN='...' \
npx -y --package mr-magic-mcp-server http-server
```
#### CLI
```bash
# Run a one-off CLI command
GENIUS_CLIENT_ID=your_id \
npx -y --package mr-magic-mcp-server mrmagic-cli find --artist "Coldplay" --title "Yellow"
```
#### npx reference — all entrypoints
| What you want | Command |
| -------------------------------- | --------------------------------------------------------- |
| MCP stdio | `npx -y --package mr-magic-mcp-server mcp-server` |
| MCP Streamable HTTP & SSE (+SSE) | `npx -y --package mr-magic-mcp-server mcp-http-server` |
| JSON HTTP automation | `npx -y --package mr-magic-mcp-server http-server` |
| CLI | `npx -y --package mr-magic-mcp-server mrmagic-cli --help` |
---
### Option 3 — Global install (binaries always on PATH)
```bash
npm install -g mr-magic-mcp-server
mcp-server # MCP stdio server
mcp-http-server # Streamable HTTP & SSE MCP server (+ legacy SSE on same port)
http-server # JSON HTTP automation server
mrmagic-cli --help # CLI
```
When the binary is launched by an MCP client, it does **not** automatically read a `.env` file
unless you point to one via `MR_MAGIC_ENV_PATH`. Either pass credentials through the client
`env` block or set them as system/user environment variables.
---
### Musixmatch token for npx / ephemeral / headless installs
When running via `npx`, on Render free tier, or on any server without a browser or
persistent filesystem, the Musixmatch token cannot be captured via Playwright there.
The workflow is:
1. **Capture the token locally** (one-time on any machine with a browser):
```bash
git clone https://github.com/mrnajiboy/mr-magic-mcp-server.git
cd mr-magic-mcp-server && npm install
npm run fetch:musixmatch-token
```
After signing in, the script prints the full token JSON payload.
Copy the entire printed JSON object (the `MUSIXMATCH_DIRECT_TOKEN=...` line).
2. **Push to KV** so the server can read it on every cold start.
Set up Upstash Redis (free tier at [console.upstash.com](https://console.upstash.com/redis))
and run the push script with KV credentials. No browser needed:
```bash
UPSTASH_REDIS_REST_URL=https://xxx.upstash.io \
UPSTASH_REDIS_REST_TOKEN=your_upstash_token \
MUSIXMATCH_DIRECT_TOKEN='<paste token JSON here>' \
npm run push:musixmatch-token
```
3. **Start the server** with the same Upstash credentials — it reads the token from
KV on every cold start (no `MUSIXMATCH_DIRECT_TOKEN` needed when KV is configured):
```bash
GENIUS_DIRECT_TOKEN=... \
UPSTASH_REDIS_REST_URL=https://xxx.upstash.io \
UPSTASH_REDIS_REST_TOKEN=your_upstash_token \
npx -y --package mr-magic-mcp-server mcp-http-server
```
4. Re-run steps 1–2 only when the Musixmatch token expires (typically ~30 days).
#### Musixmatch on Render (headless, no SSH)
On Render free tier you cannot SSH in or open a browser. The recommended pattern is:
1. Run `npm run fetch:musixmatch-token` locally, copy the token JSON from the output.
2. In the Render Dashboard → **Environment** tab, set:
- `MUSIXMATCH_DIRECT_TOKEN` = `<your token JSON>` _(used as both push source and runtime override)_
- `UPSTASH_REDIS_REST_URL` = your Upstash endpoint
- `UPSTASH_REDIS_REST_TOKEN` = your Upstash token
3. Set the Render **Start Command** to:
```
npm run push:musixmatch-token && npm run server:mcp:http
```
On every (re)start, the token is pushed to Upstash then the server reads it
from KV. If `MUSIXMATCH_DIRECT_TOKEN` is unset the push step is a silent no-op.
4. When the token expires: update `MUSIXMATCH_DIRECT_TOKEN` from a fresh local
`fetch:musixmatch-token` run, trigger a redeploy on Render. Done.
> **Genius on ephemeral hosts:** Genius does not need this flow.
> Set `GENIUS_CLIENT_ID` + `GENIUS_CLIENT_SECRET` instead — the server calls the
> Genius OAuth `client_credentials` endpoint at runtime, auto-refreshes the token
> in memory, and never needs a browser, a KV store, or a captured session token.
## Environment Variables
Copy `.env.example` to `.env` (or inject via your platform dashboard). Variables are
grouped below by purpose.
### Genius credentials
| Variable | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `GENIUS_CLIENT_ID` | OAuth client ID for auto-refresh (recommended). Get from [genius.com/api-clients](https://genius.com/api-clients). |
| `GENIUS_CLIENT_SECRET` | OAuth client secret for auto-refresh (recommended). |
| `GENIUS_DIRECT_TOKEN` | Static direct bearer token. Used when client credentials are unavailable. |
Token resolution order (first match wins):
1. In-memory runtime cache
2. Auto-refresh via `GENIUS_CLIENT_ID` + `GENIUS_CLIENT_SECRET` ← **recommended**
3. `GENIUS_DIRECT_TOKEN` env var (static, no auto-refresh)
4. KV store — Upstash Redis or Cloudflare KV (written automatically by auto-refresh)
5. On-disk `.cache/genius-token.json` (local dev only)
### Musixmatch credentials
| Variable | Description |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `MUSIXMATCH_DIRECT_TOKEN` | Static bearer token. Recommended for production / ephemeral hosts. Also used as push source by `push:musixmatch-token`. |
| `MUSIXMATCH_TOKEN_KV_KEY` | KV key name for the token store. Default: `mr-magic:musixmatch-token`. |
| `MUSIXMATCH_TOKEN_KV_TTL_SECONDS` | Token TTL in the KV store (seconds). Default: `2592000` (30 days). |
| `MUSIXMATCH_TOKEN_CACHE` | Path to the on-disk cache file. Default: `.cache/musixmatch-token.json`. |
| `MUSIXMATCH_AUTO_FETCH` | Set to `1` to attempt headless token re-fetch when no token is found. |
Token resolution order (first match wins):
1. **Env var** — `MUSIXMATCH_DIRECT_TOKEN`
2. **KV store** — Upstash Redis (priority 1) or Cloudflare KV (priority 2)
3. **On-disk cache** — `.cache/musixmatch-token.json` (local dev / persistent servers)
### Export and storage
| Variable | Default | Description |
| ----------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `MR_MAGIC_EXPORT_BACKEND` | `local` | Storage backend: `local` \| `inline` \| `redis`. |
| `MR_MAGIC_EXPORT_DIR` | `exports/` | Absolute path for local exports. Required when backend is `local`. |
| `MR_MAGIC_EXPORT_TTL_SECONDS` | `3600` | TTL for `local` and `redis` backends (ignored for `inline`). |
| `MR_MAGIC_DOWNLOAD_BASE_URL` | _(none)_ | Public base URL for download links, e.g. `https://lyrics.example.com`. |
| `MR_MAGIC_INLINE_PAYLOAD_MAX_CHARS` | `1500` | Character threshold at which `build_catalog_payload` auto-promotes to `reference` transport when `omitInlineLyrics` is `true`. |
| `UPSTASH_REDIS_REST_URL` | — | Upstash Redis KV backend URL. Also used by the export backend when `MR_MAGIC_EXPORT_BACKEND=redis` — set once, used for both. |
| `UPSTASH_REDIS_REST_TOKEN` | — | Upstash Redis KV bearer token. Takes precedence over Cloudflare KV when both are set. |
| `CF_API_TOKEN` | — | Cloudflare API token with `KV:Edit` permission (Cloudflare KV backend). |
| `CF_ACCOUNT_ID` | — | Cloudflare account ID (Cloudflare KV backend). |
| `CF_KV_NAMESPACE_ID` | — | Cloudflare KV namespace ID (Cloudflare KV backend). |
### Server and runtime
| Variable | Default | Description |
| -------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PORT` | `3444` / `3333` | Override server port. On Render this is set automatically (default `10000`). |
| `LOG_LEVEL` | `info` | Verbosity: `error` \| `warn` \| `info` \| `debug`. |
| `MR_MAGIC_QUIET_STDIO` | `0` | Set to `1` to suppress non-error stdout logs (forces `LOG_LEVEL=error`). Recommended under stdio MCP clients. |
| `MR_MAGIC_HTTP_TIMEOUT_MS` | `10000` | Global outbound HTTP timeout in milliseconds. |
| `MR_MAGIC_ROOT` | _(project root)_ | Override the project root used for `.env` and `.cache` path resolution. |
| `MR_MAGIC_ENV_PATH` | _(auto)_ | Point to a specific `.env` file instead of `<project root>/.env`. |
| `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. |
| `MR_MAGIC_SESSIONLESS` | `0` | Set to `1` to force **sessionless mode** on the MCP Streamable HTTP & SSE server — each request is handled by a fresh, temporary server/transport with no in-memory session state. Auto-enabled on Render (see below). |
### Airtable
| Variable | Description |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `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. |
### Melon
| Variable | Description |
| -------------- | --------------------------------------------------------------------------------------------------- |
| `MELON_COOKIE` | Optional. Pin a session cookie for consistent results. Anonymous access generally works without it. |
### Diagnostics and debugging
| Variable | Default | Description |
| ------------------------------- | ------- | -------------------------------------------------------------------------------------------- |
| `MR_MAGIC_MCP_HTTP_DIAGNOSTICS` | `0` | Set to `1` to log enriched request metadata at the Streamable HTTP & SSE transport boundary. |
| `MR_MAGIC_LOG_TOOL_ARGS_CHUNKS` | `0` | Set to `1` to emit chunk-by-chunk MCP tool argument previews for truncation debugging. |
| `MR_MAGIC_TOOL_ARG_CHUNK_SIZE` | `400` | Chunk size (chars) used when chunk logging is enabled. |
| `MR_MAGIC_SDK_REPRO_HTTP_DEBUG` | `0` | Set to `1` for verbose HTTP traces in the SDK repro harness script. |
## Provider Credentials
### Genius
Genius credentials are resolved in this order — the first available source wins:
1. **Auto-refresh** (`GENIUS_CLIENT_ID` + `GENIUS_CLIENT_SECRET`) — the server calls
the Genius OAuth `client_credentials` endpoint at runtime and keeps the token
refreshed in memory. **Recommended for all deployments**, including Render and
ephemeral hosts. No disk, no scripts, no manual token copying.
2. **Direct token** (`GENIUS_DIRECT_TOKEN`) — a static bearer token. Works
everywhere but does not auto-refresh. Update by redeploying with a new value.
3. **Cache token** (`.cache/genius-token.json`) — written by `npm run fetch:genius-token`.
Only suitable for local dev with a persistent filesystem.
### Musixmatch
Musixmatch uses a captured browser session token. There is no OAuth callback.
**For production / ephemeral hosts (Render, containers):**
Set `MUSIXMATCH_DIRECT_TOKEN` directly in your environment. This is the highest-priority
env var option and the only reliable one when the filesystem may be wiped between restarts.
**For local development:**
Run the fetch script once — it opens a Playwright-controlled Chromium window, signs
you in, and writes the token to `.cache/musixmatch-token.json`:
```bash
npm run fetch:musixmatch-token
```
The workflow:
1. Run the script on any machine that can open a browser.
2. Sign in with Musixmatch (Google sign-in works) when prompted.
3. After the redirect to `https://www.musixmatch.com/discover`, the script prints
the captured token and writes the cache file.
4. **For remote deployments:** copy the `token` value from the printed JSON and set
it as `MUSIXMATCH_DIRECT_TOKEN` in your platform environment. Do **not** rely on
the cache file surviving restarts on ephemeral hosts.
> **Developer accounts:** Get API access from [developer.musixmatch.com](https://developer.musixmatch.com)
> and set the resulting token as `MUSIXMATCH_DIRECT_TOKEN`.
>
> **Public accounts:** Visit [auth.musixmatch.com](https://auth.musixmatch.com), sign in,
> and capture the token using the script above.
>
> ⚠️ **WARNING:** Calling the API from an unauthorized account may result in a ban.
### Melon
Fetching Melon endpoints works anonymously. If `MELON_COOKIE` is blank, the server
requests session cookies automatically. Set `MELON_COOKIE` to a complete cookie header
string only when you need pinned, reproducible sessions.
## Export and Download Configuration
The `MR_MAGIC_EXPORT_BACKEND` variable controls where formatted lyrics are stored:
- **`local`** (default) — writes files to `MR_MAGIC_EXPORT_DIR` (or `exports/` when
unset). Make sure the target directory is writable. The `export_lyrics` tool also
returns the raw `content` field so clients can inline results when file writes fail.
For global or npx CLI usage, the default `exports/` directory is relative to the
directory where you run `mrmagic-cli`, not the npm package install directory.
- **`inline`** — skips disk writes entirely. Each export is returned in the tool
response with `content` populated and `skipped: true` to signal that persistence
was intentionally bypassed.
- **`redis`** — stores exports in Upstash. Requires `UPSTASH_REDIS_REST_URL`,
`UPSTASH_REDIS_REST_TOKEN`, and `MR_MAGIC_DOWNLOAD_BASE_URL`.
For Redis exports, `MR_MAGIC_DOWNLOAD_BASE_URL` must be the publicly reachable base URL
of the server that will serve the download links (not the Upstash URL),
e.g. `https://lyrics.example.com`. Download links are built as
`{base_url}/downloads/{id}/{ext}`.
Both HTTP servers serve `/downloads/:id/:ext` routes:
- **`server:mcp:http`** (port `3444`) — the Streamable HTTP & SSE MCP server includes its
own `/downloads` route. If you are already running this server, no additional HTTP
server is needed for Redis exports on Render or any remote deployment.
- **`server:http`** (port `3333`) — the JSON HTTP automation server also exposes the
same route and remains the right choice if you are running `server:mcp` (stdio) only
or want a standalone HTTP service for download links.
For local testing against the MCP HTTP server:
```bash
MR_MAGIC_DOWNLOAD_BASE_URL=http://127.0.0.1:3444
```
Or against the JSON HTTP server:
```bash
MR_MAGIC_DOWNLOAD_BASE_URL=http://127.0.0.1:3333
```
For global installs, start the JSON HTTP download server with the same persisted env
file or environment variables used for export commands:
```bash
mrmagic-cli server --port 3333
```
Download URLs are only needed for the `redis` export backend. Local exports return
`file://` URLs and write files directly to `MR_MAGIC_EXPORT_DIR` or `./exports`.
## Local Deployment
Run whichever entrypoint you need via npm scripts:
```bash
npm run server:http # JSON HTTP automation — 127.0.0.1:3333 by default
npm run server:mcp # MCP stdio transport — ideal for local MCP clients
npm run server:mcp:http # Streamable HTTP & SSE MCP — 127.0.0.1:3444 by default
npm run cli -- --help # CLI entrypoint
```
Set provider tokens via `.env` before running. `dotenv` dependency is for local convenience only —
production environments should inject vars directly.
## Remote Deployment
Install dependencies and start the desired transport:
```bash
npm ci
npm run server:mcp:http # or server:http / server:mcp
```
Use a process manager (systemd, PM2, Docker `CMD`, etc.) to keep servers running.
### Deploying on Render
Both HTTP servers (`server:mcp:http` and `server:http`) are ready for Render with
no extra network configuration. Render automatically sets:
| Variable | Value |
| -------------------------- | ------------------------------------------------------ |
| `RENDER` | `"true"` |
| `PORT` | `10000` (default; overridable in the Render Dashboard) |
| `RENDER_EXTERNAL_HOSTNAME` | Your service hostname, e.g. `myapp.onrender.com` |
When `RENDER=true` is detected, the server binds to `0.0.0.0` automatically and reads
the platform-assigned `PORT`. No manual `HOST` or `PORT` configuration is needed.
The `RENDER_EXTERNAL_HOSTNAME` is automatically added to the DNS rebinding `allowedHosts`
list so the MCP SDK does not emit host-validation warnings.
Recommended Render service settings:
- **Start Command:** `npm run server:mcp:http`
- **Environment:** set provider credentials (`GENIUS_CLIENT_ID`, `GENIUS_CLIENT_SECRET`,
`MUSIXMATCH_DIRECT_TOKEN`, etc.) in the Render Dashboard → Environment tab
- **Health Check Path:** `/health` (returns `{ "status": "ok", "providers": [...] }`)
> For custom domains, add them to `MR_MAGIC_ALLOWED_HOSTS` (comma-separated) in
> your Render environment so the DNS rebinding protection accepts requests with
> those `Host` headers.
#### Sessionless mode on Render (automatic)
When `RENDER=true` is detected, the MCP Streamable HTTP & SSE server automatically operates
in **sessionless mode**. This is essential for multi-instance deployments where Render
routes requests across several processes:
- An `initialize` request served by **Instance A** would store the session in A's
in-memory `Map`. A follow-up `tools/list` call routed to **Instance B** cannot
find that session and returns `{"error": "Session not found. …"}`.
- In sessionless mode, every request — `initialize`, `tools/list`, `tools/call`, etc.
— is handled by a fresh, short-lived `Server + StreamableHTTPServerTransport` pair.
No `Mcp-Session-Id` header is issued and no session state is stored. Each request
is fully self-contained and works correctly regardless of which instance handles it.
You do **not** need to set `MR_MAGIC_SESSIONLESS=1` manually on Render — it is
auto-enabled via the platform-injected `RENDER` env var. Set `MR_MAGIC_SESSIONLESS=1`
explicitly on other multi-instance platforms (ECS, Fly.io, Railway, etc.) where
a similar load-balanced, stateless deployment is used.
### Transport selection
| Transport | Command | Use case |
| ------------------------- | ------------------------- | ----------------------------------- |
| MCP stdio | `npm run server:mcp` | Local MCP clients that speak stdio |
| MCP Streamable HTTP & SSE | `npm run server:mcp:http` | Remote MCP clients |
| JSON HTTP automation | `npm run server:http` | Container / remote automations |
| CLI | `npm run cli` | Ad-hoc / SSH / CI one-shot commands |
## HTTP Endpoints
Both HTTP servers expose a set of plain HTTP routes in addition to their primary
transports. These are accessible without any MCP or JSON-RPC framing.
| Endpoint | Method | Server | Description |
| --------------------- | ----------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/health` | `GET` | Both | Liveness / readiness probe. Returns `{ "status": "ok", "providers": [...] }`. |
| `/downloads/:id/:ext` | `GET` | Both | Serve a Redis-backed export by ID and file extension (e.g. `plain`, `lrc`, `srt`). Returns `200 text/plain` on hit, `404` when the key is expired or missing. |
| `/mcp` | `POST/GET/DELETE` | `server:mcp:http` only | MCP **Streamable HTTP & SSE** transport endpoint (JSON-RPC 2.0). Each `initialize` request creates an independent session — reconnects work correctly. |
| `/sse` | `GET` | `server:mcp:http` only | MCP **legacy SSE** transport. Opens a server-sent event stream. For MCP clients that use the pre-Streamable HTTP & SSE protocol. |
| `/messages` | `POST` | `server:mcp:http` only | Companion to `/sse`. Routes JSON-RPC messages to the correct SSE session via `?sessionId=` query param. |
| `/` | `POST` | `server:http` only | JSON HTTP automation endpoint (action-based API). |
### `/health`
Both servers respond to `GET /health` with a JSON object indicating overall status
and per-provider readiness. Use this as your Render (or container / load-balancer)
health check path.
**Response shape:**
```json
{
"status": "ok",
"providers": [
{ "name": "lrclib", "status": "ok" },
{ "name": "genius", "status": "ok" },
{ "name": "musixmatch", "status": "missing_token" },
{ "name": "melon", "status": "ok" }
]
}
```
**MCP HTTP server** (default port `3444`):
```bash
curl -sS http://127.0.0.1:3444/health | jq
```
**JSON HTTP server** (default port `3333`):
```bash
curl -sS http://127.0.0.1:3333/health | jq
```
Provider `status` values:
| Value | Meaning |
| --------------- | -------------------------------------------------------------- |
| `ok` | Provider is configured and reachable. |
| `missing_token` | Required credential env var is not set. |
| `error` | Provider returned an unexpected error during the status probe. |
### `/downloads/:id/:ext`
Serves a Redis-backed export file by its download ID and format extension. Both
servers expose this route so the same `MR_MAGIC_DOWNLOAD_BASE_URL` works regardless
of which server you are running.
**Parameters:**
- `:id` — the opaque download ID returned in the `url` field of an export response
- `:ext` — the file format: `plain`, `lrc`, `srt`, or `romanized`
**Example** (MCP HTTP server):
```bash
curl -sS http://127.0.0.1:3444/downloads/coldplay-yellow-1741234567890/plain
```
**Example** (JSON HTTP server):
```bash
curl -sS http://127.0.0.1:3333/downloads/coldplay-yellow-1741234567890/plain
```
Responses:
- `200 text/plain` — export content served directly
- `404` — key expired or never written (`MR_MAGIC_EXPORT_TTL_SECONDS` controls TTL,
default `3600` seconds)
- `400` — malformed path (missing ID or extension)
- `500` — Redis lookup error
> Requires `MR_MAGIC_EXPORT_BACKEND=redis` and valid `UPSTASH_*` credentials.
> For local and inline backends, exports are returned directly in the tool response
> and this route is not used.
## MCP Tools
Both the stdio and Streamable HTTP & SSE transports expose the same tool registry:
| Tool | Purpose |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `find_lyrics` | Fetch best lyrics (prefers synced) plus metadata and payload from direct track metadata, or resolve a prior provider reference / selected match. |
| `find_synced_lyrics` | Like `find_lyrics` but rejects plain-only results; also accepts direct track metadata, a provider reference, or a selected match. |
| `search_lyrics` | Return grouped, preview-only candidate matches across all providers, including reusable provider references. |
| `search_provider` | Query a single named provider and return flat, preview-only matches plus reusable provider references. |
| `get_provider_status` | Report readiness and notes for each provider. |
| `format_lyrics` | Format lyrics in memory (optional romanization) for display from direct track metadata, a provider reference, or a selected match. |
| `export_lyrics` | Write plain / LRC / SRT / romanized files to the export backend from direct track metadata, a provider reference, or a selected match. |
| `select_match` | Pick a single prior search result from grouped `items`, flat `matches`, or a direct `match`, using provider/index/synced filters. |
| `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. |
| `push_catalog_to_airtable` | Write catalog records to Airtable server-side — lyrics never pass through LLM arguments. Requires `AIRTABLE_PERSONAL_ACCESS_TOKEN`. |
| `runtime_status` | Snapshot provider readiness plus relevant env vars. |
### MCP search result behavior
`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.
Each returned result is designed for follow-up tool calls and includes:
- core metadata such as provider, title, artist, album, duration, source URL, confidence, and synced/plain-only flags
- preview snippets (`plainPreview` / `syncedPreview`) when available
- a reusable `reference` object that can be passed back into downstream tools for exact result recall
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.
### MCP provider-reference workflow
After a search step, the following tools can resolve lyrics from **either**:
- direct `track` metadata
- a provider `reference` returned by `search_lyrics` or `search_provider`
- a selected `match` object from prior search output
This applies to:
- `find_lyrics`
- `find_synced_lyrics`
- `build_catalog_payload`
- `format_lyrics`
- `export_lyrics`
### `select_match` in the workflow
`select_match` is the bridge between preview-only search results and the tools that resolve full lyrics.
It accepts any of these input shapes:
- `items` — grouped provider results returned by `search_lyrics`
- `matches` — flat results returned by `search_provider` (or other flattened match arrays)
- `match` — a single previously returned result passed through directly
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.
### Example workflow: search → select → build/export
1. Call `search_lyrics` to get grouped, preview-only results plus provider `reference` objects.
2. Call `select_match` with the returned `items` to choose one result.
3. Pass the returned `match` into `build_catalog_payload`, `find_lyrics`, `format_lyrics`, or `export_lyrics`.
4. Optionally reuse the nested `match.result.reference` later to resolve the exact same provider result again without repeating the original broad search.
Example sequence:
```json
{
"track": { "artist": "Coldplay", "title": "Yellow" }
}
```
→ `search_lyrics`
```json
{
"items": [
{
"provider": "lrclib",
"results": [
{
"title": "Yellow",
"artist": "Coldplay",
"synced": true,
"reference": {
"provider": "lrclib",
"providerId": "...",
"fingerprint": "..."
}
}
]
}
],
"criteria": { "requireSynced": true, "index": 0 }
}
```
→ `select_match`
```json
{
"match": {
"provider": "lrclib",
"result": {
"title": "Yellow",
"artist": "Coldplay",
"reference": {
"provider": "lrclib",
"providerId": "...",
"fingerprint": "..."
}
}
},
"options": {
"omitInlineLyrics": true,
"lyricsPayloadMode": "payload"
}
}
```
→ `build_catalog_payload`
Or export the same exact result later with:
```json
{
"reference": {
"provider": "lrclib",
"providerId": "...",
"fingerprint": "..."
},
"options": {
"formats": ["plain", "lrc", "srt"]
}
}
```
→ `export_lyrics`
## Airtable Integration
Mr. Magic routes lyrics entirely server-side so long lyric text never passes through
LLM tool-call arguments. This eliminates the JSON truncation and malformed-request
errors that occur when multiline Korean / CJK lyrics are interpolated into payloads.
### How it works
1. **Call `build_catalog_payload`** for each song. The response contains a
`lyricsCacheKey` (e.g. `kda-ill-show-you`) that identifies the resolved lyrics
in the server's in-memory LRU cache (20 entries, shared across the MCP session).
2. **Create Airtable records** (Song title, Spotify link, etc.) using your Airtable
MCP's bulk create tools (up to 10 records per call). Capture the `recordId`
returned for each created record.
3. **Call `push_catalog_to_airtable`** with `recordId`, `lyricsFieldId`, and
`lyricsCacheKey`. The server looks up the cached lyrics and calls the Airtable
REST API directly. Lyric text **never leaves the server process** as an MCP argument.
### `push_catalog_to_airtable` call shape
```json
{
"baseId": "appeBUkVEp3N4RT0C",
"tableId": "tbl0y5XHFXpjUJXHu",
"recordId": "rec1234567890abcd",
"fields": {},
"lyricsFieldId": "fldHV1qmPYmsvglff",
"lyricsCacheKey": "kda-ill-show-you",
"preferRomanized": true
}
```
Set `"splitLyricsUpdate": true` when the combined create + lyrics payload is too large —
this forces a two-step create → PATCH so the full payload is never sent in one request.
### Bundled prompt template
`prompts/airtable-song-importer.md` (shipped in the source package) is a ready-to-use
system prompt for MCP assistants that bulk-import songs into Airtable. It covers:
- Phased execution: resolve → bulk create → write lyrics → SRT export
- Bulk record creation up to 10 records per Airtable MCP call
- Spotify link resolution via the Spotify MCP
- Romanized lyric priority for K-pop / CJK content
- `splitLyricsUpdate` fallback for oversized payloads
Copy the file contents into your MCP client's system prompt to deploy immediately.
### Safe lyric payload handoff
To avoid embedding raw lyric text in tool-call arguments, request a structured payload:
```json
{
"track": { "artist": "K/DA", "title": "I'll Show You" },
"options": {
"omitInlineLyrics": true,
"lyricsPayloadMode": "payload"
}
}
```
Option reference:
- `omitInlineLyrics: true` — removes `lyrics`, `plainLyrics`, and `romanizedPlainLyrics`
from the response, keeping it compact.
- `lyricsPayloadMode: "payload"` — adds a `lyricsPayload` object with the full text
inline (`transport: "inline"`). May auto-promote to `"reference"` for long lyrics when
`omitInlineLyrics` is also `true`.
- `lyricsPayloadMode: "reference"` — stores lyrics via the export backend and returns
a `lyricsPayload.reference` object with `filePath` or `url` instead of raw text.
- `airtableSafePayload: true` — adds `lyricsPayload.airtableEscapedContent` (quotes /
backslashes / newlines pre-escaped) and prefers compact / reference-style handoff.
**Important:** `tools/call.params.arguments` must be a plain JSON object. Do not
pre-serialize arguments into a string — `build_catalog_payload` and `select_match`
reject stringified payloads.
### Calling patterns
Preferred (MCP SDK):
```js
await client.callTool({
name: 'build_catalog_payload',
arguments: {
track: { artist: 'K/DA', title: "I'll Show You" },
options: { omitInlineLyrics: true, lyricsPayloadMode: 'payload', airtableSafePayload: true }
}
});
```
Also valid (raw `fetch`, object-based):
```js
await fetch('http://127.0.0.1:3444/mcp', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/event-stream' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'tools/call',
params: {
name: 'build_catalog_payload',
arguments: {
track: { artist: 'K/DA', title: "I'll Show You" },
options: { omitInlineLyrics: true, lyricsPayloadMode: 'payload', airtableSafePayload: true }
}
}
})
});
```
**Avoid:** manual JSON string templates that interpolate multiline lyrics.
### Debugging truncated arguments
Enable chunk logging to diagnose malformed / truncated MCP tool arguments:
```bash
MR_MAGIC_LOG_TOOL_ARGS_CHUNKS=1
MR_MAGIC_TOOL_ARG_CHUNK_SIZE=400
LOG_LEVEL=debug
```
Recommended presets:
| Scenario | `LOG_LEVEL` | `MR_MAGIC_LOG_TOOL_ARGS_CHUNKS` |
| ---------------------- | ----------- | ------------------------------- |
| Normal operation | `info` | `0` |
| General verbose | `debug` | `0` |
| Truncation diagnostics | `debug` | `1` |
## MCP Client Configuration
Mr. Magic supports two connection modes depending on where the MCP client runs:
| Mode | Transport | When to use |
| ---------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------- |
| **Local (stdio)** | `mcp-server` binary via stdin/stdout | Cline, Claude Desktop, and any client that runs locally on the same machine |
| **Remote (Streamable HTTP & SSE)** | `POST https://your-server.com/mcp` | TypingMind, browser-based clients, and any client connecting to a deployed server |
### Configuration modes at a glance
The right way to supply environment variables depends on how the server is launched:
| Config mode | How it starts | How to pass env vars | `.env` file read? |
| ---------------------------------------------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------- | ----------------- |
| **Local source** | `node src/bin/mcp-server.js` or `npm run server:mcp` | `.env` in project root, or shell exports | ✅ yes |
| **Local npx** | `npx --package mr-magic-mcp-server mcp-server` via MCP client | `env` block in MCP client config (see below) | ❌ no |
| **Persistent server** (VPS, global install, Docker with persistent FS) | `mcp-server` binary or `npm run server:mcp:http` | `.env` file **or** platform environment variables | ✅ if present |
| **Ephemeral server** (Render free tier, containers, serverless) | `npx` or process started fresh each time | Platform environment variables (Render Dashboard, Docker `--env`) | ❌ no |
> **`npx` and MCP clients:** When a local MCP client (Cline, Claude Desktop, etc.) starts the
> server via `npx`, the spawned process has **no access to your `.env` file** — your
> project root and shell environment are not inherited. Every required variable must be
> provided in the `env` block of your MCP client config. See the npx snippet below.
### Required variables by deployment type
#### Ephemeral / npx / stateless hosts (no persistent filesystem)
These variables should be passed explicitly — the server cannot fall back to `.env` or on-disk caches:
| Variable | Purpose | Required when |
| ----------------------------------------------------- | ---------------------------------------------------- | -------------------------------------------------------- |
| `GENIUS_CLIENT_ID` + `GENIUS_CLIENT_SECRET` | Genius auto-refresh (recommended) | Using Genius |
| `GENIUS_DIRECT_TOKEN` | Static Genius token (alternative) | Using Genius without OAuth credentials |
| `MUSIXMATCH_DIRECT_TOKEN` | Musixmatch token (highest priority) | Using Musixmatch |
| `UPSTASH_REDIS_REST_URL` + `UPSTASH_REDIS_REST_TOKEN` | KV store for Musixmatch token + Redis export backend | Musixmatch via KV **or** `MR_MAGIC_EXPORT_BACKEND=redis` |
| `MR_MAGIC_EXPORT_BACKEND` | Storage backend for exports (`inline` or `redis`) | Exporting lyrics; use `inline` if no Redis |
| `MR_MAGIC_DOWNLOAD_BASE_URL` | Base URL for download links | `MR_MAGIC_EXPORT_BACKEND=redis` only |
| `AIRTABLE_PERSONAL_ACCESS_TOKEN` | Airtable push tool | Using `push_catalog_to_airtable` |
| `MR_MAGIC_QUIET_STDIO` | Suppress non-error stdout (set to `1`) | stdio MCP clients — avoids JSON-RPC parse errors |
> 💡 **Musixmatch on ephemeral hosts:** The on-disk token cache (`.cache/musixmatch-token.json`)
> is **not** available when running via `npx` or on ephemeral servers. Use `MUSIXMATCH_DIRECT_TOKEN`
> directly, or configure Upstash Redis (`UPSTASH_REDIS_REST_URL` + `UPSTASH_REDIS_REST_TOKEN`) and
> run `push:musixmatch-token` once to store the token in KV. See
> [Musixmatch token for npx / ephemeral / headless installs](#musixmatch-token-for-npx--ephemeral--headless-installs).
#### Local source / persistent servers (writable filesystem present)
In addition to the provider credentials above, local and persistent deployments can also use:
| Variable | Purpose |
| ------------------------------------- | ------------------------------------------------------------------------------ |
| `MR_MAGIC_EXPORT_BACKEND=local` | Write export files to disk (default; not usable on ephemeral hosts) |
| `MR_MAGIC_EXPORT_DIR` | Directory to write local exports into |
| `MR_MAGIC_ROOT` / `MR_MAGIC_ENV_PATH` | Override project root / `.env` path resolution |
| `MUSIXMATCH_AUTO_FETCH` | Auto re-run the Playwright fetch script when no token found (requires browser) |
On-disk token caches (`.cache/genius-token.json`, `.cache/musixmatch-token.json`) are also
read automatically when a persistent filesystem is available and the above env vars are not set.
---
### Local clients (stdio)
> ⚠️ **Stdio MCP clients:** Always invoke the server binary directly — never via
> `npm run server:mcp`. The npm script preamble (`> mr-magic-mcp-server@x.x.x …`) is
> written to stdout before Node starts, and stdio MCP clients try to parse every stdout
> line as JSON-RPC, causing "Unexpected token '>'" errors on every connection.
#### npx (no clone required)
Works with any local MCP client that supports `command` / `args`. Because this package
publishes multiple binaries, you must use `--package` to name the package and then
explicitly name the `mcp-server` binary. This is the correct form — do not use
`npx -y mr-magic-mcp-server` (no binary by that name exists).
> ⚠️ **`npx` does not read your local `.env` file.** The process is spawned by the MCP
> client and has no access to your project directory or shell environment. All credentials
> and configuration must be provided in the `env` block below. For a simpler setup,
> prefer [Local repo — Cline](#local-repo--cline) which reads `.env` automatically.
```json
{
"mcpServers": {
"Mr. Magic": {
"command": "npx",
"args": ["-y", "--package", "mr-magic-mcp-server", "mcp-server"],
"env": {
"MR_MAGIC_QUIET_STDIO": "1",
"GENIUS_CLIENT_ID": "...",
"GENIUS_CLIENT_SECRET": "...",
"MUSIXMATCH_DIRECT_TOKEN": "...",
"UPSTASH_REDIS_REST_URL": "https://xxx.upstash.io",
"UPSTASH_REDIS_REST_TOKEN": "...",
"AIRTABLE_PERSONAL_ACCESS_TOKEN": "..."
}
}
}
}
```
Variable notes:
- `MR_MAGIC_QUIET_STDIO=1` — **always set this for stdio clients**. Suppresses non-error
stdout so the MCP client doesn't see log lines as JSON-RPC noise.
- `GENIUS_CLIENT_ID` + `GENIUS_CLIENT_SECRET` — recommended for auto-refresh. Use
`GENIUS_DIRECT_TOKEN` instead for a static token (no auto-refresh).
- `MUSIXMATCH_DIRECT_TOKEN` — required if using Musixmatch. Must be the full token
JSON payload (from `npm run fetch:musixmatch-token`). Omit only if you've already
pushed the token to a KV store via `push:musixmatch-token` (then supply `UPSTASH_*`
and the KV lookup handles it at runtime).
- `UPSTASH_REDIS_REST_URL` + `UPSTASH_REDIS_REST_TOKEN` — optional but recommended:
enables KV-backed Musixmatch token storage (so you can refresh the token without
updating the client config) and unlocks the `redis` export backend.
- `AIRTABLE_PERSONAL_ACCESS_TOKEN` — only required if using `push_catalog_to_airtable`.
Minimal config (Genius + Musixmatch, no Airtable, no Redis):
```json
{
"mcpServers": {
"Mr. Magic": {
"command": "npx",
"args": ["-y", "--package", "mr-magic-mcp-server", "mcp-server"],
"env": {
"MR_MAGIC_QUIET_STDIO": "1",
"GENIUS_CLIENT_ID": "...",
"GENIUS_CLIENT_SECRET": "...",
"MUSIXMATCH_DIRECT_TOKEN": "..."
}
}
}
}
```
#### Global install
After `npm install -g mr-magic-mcp-server`, the `mcp-server` binary is on `PATH`.
When launched by an MCP client, the global binary **can** read a `.env` file if
`MR_MAGIC_ENV_PATH` points to one — otherwise pass credentials via the `env` block
just like the `npx` config above, or set them as system/user-level environment
variables so they're available to all spawned processes.
```json
{
"mcpServers": {
"Mr. Magic": {
"command": "mcp-server",
"env": {
"MR_MAGIC_QUIET_STDIO": "1",
"GENIUS_CLIENT_ID": "...",
"GENIUS_CLIENT_SECRET": "...",
"MUSIXMATCH_DIRECT_TOKEN": "..."
}
}
}
}
```
Or, if you keep a `.env` file somewhere on disk:
```json
{
"mcpServers": {
"Mr. Magic": {
"command": "mcp-server",
"env": {
"MR_MAGIC_QUIET_STDIO": "1",
"MR_MAGIC_ENV_PATH": "/Users/you/.config/mr-magic/.env"
}
}
}
}
```
#### Local repo — Cline
Cline supports `cwd`, so you can invoke `node` directly. The server reads `.env`
from the project root automatically — no `env` block needed for credentials you've
already set there (though you may still want `MR_MAGIC_QUIET_STDIO`):
```json
{
"mcpServers": {
"Mr. Magic": {
"disabled": false,
"timeout": 60,
"type": "stdio",
"command": "node",
"args": ["src/bin/mcp-server.js"],
"cwd": "/Users/you/Documents/Code/MCP/mr-magic-mcp-server",
"env": {
"MR_MAGIC_QUIET_STDIO": "1"
}
}
}
}
```
#### Local repo — clients without `cwd` support
For local clients that don't support a working-directory option, use a shell wrapper.
The `cd` sets the project root so `.env` is found automatically:
```json
{
"mcpServers": {
"Mr. Magic": {
"command": "/bin/sh",
"args": ["-c", "cd /Users/you/Code/mr-magic-mcp-server && node src/bin/mcp-server.js"],
"env": {
"MR_MAGIC_QUIET_STDIO": "1"
}
}
}
}
```
---
### Remote clients (Streamable HTTP & SSE)
When Mr. Magic is deployed on a remote host (Render, VPS, etc.), connect via the
Streamable HTTP & SSE MCP endpoint (`/mcp`). Credentials are configured server-side via
environment variables — no `env` block is needed in the client config.
#### Generic remote client (URL-based config)
Any client that accepts a plain MCP endpoint URL:
```
https://your-server.com/mcp
```
#### Legacy SSE clients
Some older MCP clients use the pre-Streamable HTTP & SSE SSE protocol instead of `POST /mcp`.
For those, use the legacy SSE endpoint:
```
GET https://your-server.com/sse ← opens the event stream
POST https://your-server.com/messages ← sends JSON-RPC messages
```
The server supports both protocols simultaneously — no restart or reconfiguration needed.
#### TypingMind
TypingMind connects to remote MCP servers through its **MCP Connector** browser
extension (Chrome / Edge). Once your server is deployed:
1. Open TypingMind → **Plugins** → **MCP Servers** → **Add MCP Server**.
2. Set the endpoint URL to your deployed server's `/mcp` path, e.g.:
```
https://your-server.com/mcp
```
3. Leave authentication blank (credentials are set server-side via environment
variables on the deployed instance).
4. Save and enable the server.
> **"Update required. Please restart your MCP Connector…"**
>
> This message is displayed by the **TypingMind MCP Connector extension** itself —
> it is **not** a Mr. Magic error. It means the installed version of the extension
> predates remote MCP server support. Fix: restart the MCP Connector extension (click
> the extension icon → restart, or disable and re-enable it in your browser's
> Extensions settings). If the message persists, update the extension from the Chrome /
> Edge Web Store. No changes to Mr. Magic or your server configuration are required.
## CLI
A single CLI entrypoint (`mrmagic-cli`) is published with the package. Inside the
local repo use `npm run cli -- <subcommand>` unless you have run `npm link` or
installed globally.
Global CLI options:
- `--env-path <path>` / `--env-file <path>` — load credentials from a custom `.env` file
before running the command. This is useful for global installs, `npm link`, and `npx`
usage where the package install directory is not your project directory.
- `--save-env-path` — persist the provided `--env-path` to
`~/.config/mrmagic-cli/config.json` so future `mrmagic-cli` commands load the same
credentials file automatically.
### Commands
| Command | Purpose | Notable flags |
| ------------------------------------ | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `mrmagic-cli search` | List candidates across providers without downloading. | `--artist`, `--title`, `--provider`, `--duration`, `--show-all`, `--pick` |
| `mrmagic-cli find` | Resolve best lyric (prefers synced) and print / export. | `--providers`, `--synced-only`, `--export`, `--format`, `--output`, `--no-romanize`, `--choose`, `--index` |
| `mrmagic-cli export` | Resolve best lyric and write export files directly. | `--providers`, `--synced-only`, `--format`, `--output`, `--no-romanize` |
| `mrmagic-cli select` | Pick first match from a prioritized provider list. | `--providers`, `--artist`, `--title`, `--require-synced` |
| `mrmagic-cli server` | Start the JSON automation API. | `--host`, `--port`, `--remote` |
| `mrmagic-cli server:mcp` | Start the MCP stdio server. | — |
| `mrmagic-cli server:mcp:http` | Start the Streamable HTTP & SSE MCP server. | `--host`, `--port`, `--remote`, `--sessionless` |
| `mrmagic-cli search-provider` | Query a single provider only. | `--provider`, `--artist`, `--title` |
| `mrmagic-cli status` | Print provider readiness. | — |
| `mrmagic-cli fetch:musixmatch-token` | Launch the browser workflow to fetch/cache a Musixmatch token. | `--headless`, `--browser`, `--session-dir` |
| `mrmagic-cli push:musixmatch-token` | Push an existing Musixmatch token to configured storage backends. | `--token` |
### Examples
```bash
# Search all providers
npm run cli -- search --artist "BLACKPINK" --title "Kill This Love"
# Global/custom install with an explicit credential file
mrmagic-cli --env-path /absolute/path/to/.env find --artist "Nayeon" --title "POP!"
# Save that credential file path once for future global CLI commands
mrmagic-cli --env-path /absolute/path/to/.env --save-env-path status
# Find best lyric (prefers synced LRC)
npm run cli -- find --artist "Nayeon" --title "POP!"
# Export plain text and SRT files for the best match
npm run cli -- export --artist "Nayeon" --title "POP!" --format plain --format srt --output ./exports
# Global install: local export files are written to ./exports relative to your shell's current directory
mrmagic-cli export --artist "Nayeon" --title "POP!" --format srt
# Global install: Redis-backed download URLs served by the JSON HTTP server
MR_MAGIC_EXPORT_BACKEND=redis \
MR_MAGIC_DOWNLOAD_BASE_URL=http://127.0.0.1:3333 \
mrmagic-cli export --artist "Nayeon" --title "POP!" --format srt
mrmagic-cli server --port 3333
# Pick first synced match from a prioritized provider list
npm run cli -- select --providers lrclib,genius --artist "Nayeon" --title "POP!" --require-synced
# Fetch and cache a Musixmatch token through the CLI
npm run cli -- fetch:musixmatch-token
# Push an existing Musixmatch token through the CLI
npm run cli -- push:musixmatch-token --token '{"message":...}'
# Start JSON automation API on a custom port
mrmagic-cli server --port 4000
```
### npm argument forwarding
Both of these forms work:
```bash
npm run cli search --artist "K/DA" --title "I'll Show You"
npm run cli -- search --artist "K/DA" --title "I'll Show You"
```
For direct binary usage: `mrmagic-cli search --artist "K/DA" --title "I'll Show You"`.
## Manual Testing
Automated checks:
```bash
npm run test # full bundled test runner
node src/tests/mcp-tools.test.js # raw MCP integration harness
npm run repro:mcp:arg-boundary # JSON-RPC argument boundary repro
npm run repro:mcp:arg-boundary:sdk # SDK client transport repro
npm run lint
npm run format:check
```
### JSON HTTP server (`server:http`)
Start the server:
```bash
npm run server:http
```
Default base URL: `http://127.0.0.1:3333`
Accepted requests:
- `GET /health`
- `POST /` with body `{ "action": "find|findSynced|search", "track": {...}, "options": {...} }`
#### Health check
```bash
curl -sS http://127.0.0.1:3333/health | jq
```
#### Basic lyric lookup (`action=find`)
```bash
curl -sS -X POST http://127.0.0.1:3333 \
-H 'Content-Type: application/json' \
-d '{"action":"find","track":{"artist":"Coldplay","title":"Yellow"},"options":{}}' | jq
```
#### Synced-only lookup (`action=findSynced`)
```bash
curl -sS -X POST http://127.0.0.1:3333 \
-H 'Content-Type: application/json' \
-d '{"action":"findSynced","track":{"artist":"Coldplay","title":"Yellow"},"options":{}}' | jq
```
#### Search candidates (`action=search`)
```bash
curl -sS -X POST http://127.0.0.1:3333 \
-H 'Content-Type: application/json' \
-d '{"action":"search","track":{"artist":"Coldplay","title":"Yellow"}}' | jq
```
#### Export flow
```bash
curl -sS -X POST http://127.0.0.1:3333 \
-H 'Content-Type: application/json' \
-d '{"action":"find","track":{"artist":"Coldplay","title":"Yellow"},"options":{"export":true,"formats":["plain"]}}' | jq
```
Look for `exports.plain` in the response:
- **redis backend:** `url` field present, `skipped: false`
- **local backend:** `filePath` field present
- **inline backend:** `content` field present, `skipped: true`
To fetch a Redis export download:
```bash
EXPORT_URL=$(curl -sS -X POST http://127.0.0.1:3333 \
-H 'Content-Type: application/json' \
-d '{"action":"find","track":{"artist":"Coldplay","title":"Yellow"},"options":{"export":true,"formats":["plain"]}}' \
| jq -r '.exports.plain.url')
curl -sS "$EXPORT_URL" | head -n 10
```
### MCP Streamable HTTP & SSE server (`server:mcp:http`)
Start the server:
```bash
npm run server:mcp:http
```
Default endpoint: `http://127.0.0.1:3444/mcp`
All requests are JSON-RPC 2.0. Required headers:
```
Content-Type: application/json
Accept: application/json, text/event-stream
```
> Tip: use `--sessionless` for easier stateless manual testing:
> `npm run cli -- server:mcp:http --sessionless`
#### Health check
```bash
curl -sS http://127.0.0.1:3444/health | jq
```
#### List tools
```bash
curl -sS -X POST http://127.0.0.1:3444/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq
```
#### `find_lyrics`
Accepts either direct `track` metadata, a provider `reference` from prior search output, or a selected `match`.
```bash
curl -sS -X POST http://127.0.0.1:3444/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"find_lyrics","arguments":{"track":{"artist":"Coldplay","title":"Yellow"}}}}' | jq
```
#### `find_synced_lyrics`
Same lookup inputs as `find_lyrics`, but only returns timestamped results.
```bash
curl -sS -X POST http://127.0.0.1:3444/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"find_synced_lyrics","arguments":{"track":{"artist":"Coldplay","title":"Yellow"}}}}' | jq
```
#### `build_catalog_payload` — inline lyrics
Accepts direct `track` metadata, a reusable `reference`, or a selected `match` from prior search output.
```bash
curl -sS -X POST http://127.0.0.1:3444/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{
"jsonrpc":"2.0","id":4,"method":"tools/call",
"params":{"name":"build_catalog_payload","arguments":{"track":{"artist":"K/DA","title":"I'\''LL SHOW YOU"},"options":{"preferRomanized":false}}}
}' | jq
```
#### `build_catalog_payload` — Airtable-safe mode
```bash
curl -sS -X POST http://127.0.0.1:3444/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{
"jsonrpc":"2.0","id":5,"method":"tools/call",
"params":{"name":"build_catalog_payload","arguments":{"track":{"artist":"K/DA","title":"I'\''LL SHOW YOU"},"options":{"omitInlineLyrics":true,"lyricsPayloadMode":"payload","airtableSafePayload":true}}}
}' | jq
```
#### `search_lyrics`
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.
```bash
curl -sS -X POST http://127.0.0.1:3444/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"search_lyrics","arguments":{"track":{"artist":"Coldplay","title":"Yellow"}}}}' | jq
```
#### `search_provider`
Returns a flat `matches` array with the same preview-only result shape used by the grouped `search_lyrics` output.
```bash
curl -sS -X POST http://127.0.0.1:3444/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"search_provider","arguments":{"provider":"lrclib","track":{"artist":"Coldplay","title":"Yellow"}}}}' | jq
```
#### `format_lyrics`
Accepts direct `track` metadata, a reusable `reference`, or a selected `match`.
```bash
curl -sS -X POST http://127.0.0.1:3444/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"format_lyrics","arguments":{"track":{"artist":"aespa","title":"Supernova"},"options":{"includeSynced":true}}}}' | jq
```
#### `export_lyrics`
Accepts direct `track` metadata, a reusable `reference`, or a selected `match`.
```bash
curl -sS -X POST http://127.0.0.1:3444/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-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
```
#### `get_provider_status`
```bash
curl -sS -X POST http://127.0.0.1:3444/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"get_provider_status","arguments":{}}}' | jq
```
#### `runtime_status`
```bash
curl -sS -X POST http://127.0.0.1:3444/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"runtime_status","arguments":{}}}' | jq
```
#### `push_catalog_to_airtable`
First call `build_catalog_payload` to populate the lyric cache and capture `lyricsCacheKey`,
then pass it here — no lyric text goes through tool-call arguments:
```bash
curl -sS -X POST http://127.0.0.1:3444/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{
"jsonrpc":"2.0","id":12,"method":"tools/call",
"params":{"name":"push_catalog_to_airtable","arguments":{
"baseId":"appeBUkVEp3N4RT0C",
"tableId":"tbl0y5XHFXpjUJXHu",
"recordId":"rec1234567890abcd",
"fields":{},
"lyricsFieldId":"fldHV1qmPYmsvglff",
"lyricsCacheKey":"kda-ill-show-you",
"preferRomanized":true
}}
}' | jq
```
> Replace `baseId`, `tableId`, `recordId`, `lyricsFieldId`, and `lyricsCacheKey` with
> real values. Requires `AIRTABLE_PERSONAL_ACCESS_TOKEN`.
#### `select_match`
Use `items` for grouped `search_lyrics` output, `matches` for flat `search_provider` output, or `match` to pass through a single result directly.
```bash
curl -sS -X POST http://127.0.0.1:3444/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{
"jsonrpc":"2.0","id":13,"method":"tools/call",
"params":{"name":"select_match","arguments":{
"matches":[{"provider":"lrclib","result":{"title":"Yellow","artist":"Coldplay","synced":true,"plainOnly":false}}],
"criteria":{"requireSynced":true,"index":0}
}}
}' | jq
```
Example using grouped `items` from `search_lyrics`:
```bash
curl -sS -X POST http://127.0.0.1:3444/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{
"jsonrpc":"2.0","id":14,"method":"tools/call",
"params":{"name":"select_match","arguments":{
"items":[{"provider":"lrclib","results":[{"title":"Yellow","artist":"Coldplay","synced":true,"reference":{"provider":"lrclib","providerId":"123","fingerprint":"abc"}}]}],
"criteria":{"requireSynced":true,"index":0}
}}
}' | jq
```
> **MCP tool response shape:**
>
> - `result.structuredContent` — machine-friendly object (all fields, full values)
> - `result.content[0].text` — complete pretty-printed JSON (identical to `structuredContent`)
>
> Both channels carry the same complete payload. Programmatic consumers should prefer
> `structuredContent`; LLM agents reading `content[0].text` get the full JSON string.
### Running both servers side-by-side
You may want both servers running at the same time — for example, to serve JSON HTTP
automations (`server:http`) alongside MCP tool calls (`server:mcp:http`), or to expose
both a REST API and an MCP endpoint under one deployment.
```bash
# Terminal 1
npm run server:http # JSON HTTP automation — port 3333
# Terminal 2
npm run server:mcp:http # Streamable HTTP & SSE MCP — port 3444
```
> **Note:** Running both is **not** required for Redis exports. The MCP HTTP server
> (`server:mcp:http`) includes its own `/downloads/:id/:ext` route, so a single
> `server:mcp:http` instance is self-sufficient for Redis-backed download links.
> Only run `server:http` alongside it if you also need the JSON HTTP automation API.
## Changelog
See [`CHANGELOG.md`](CHANGELOG.md) for a full history of changes.
## License
[MIT](LICENSE)
I am not and cannot be held liable for any infringement or ban from services
that could occur as a result of using this software. Your usage is solely
your responsibility. Godspeed.
© 2026 Kenyatta Naji Johnson-Adams
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues