mr-magic-mcp-server 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example CHANGED
@@ -118,6 +118,7 @@ MR_MAGIC_TOOL_ARG_CHUNK_SIZE=400 # Chunk size in chars (only used when LOG_TOOL
118
118
 
119
119
  # Streamable HTTP MCP transport diagnostics
120
120
  MR_MAGIC_MCP_HTTP_DIAGNOSTICS=0 # Set to 1 to log enriched request diagnostics at transport ingress
121
+ MR_MAGIC_ALLOWED_HOSTS= # Optional. Comma-separated extra allowed hostnames for DNS rebinding protection when binding to 0.0.0.0 (e.g. custom domains). RENDER_EXTERNAL_HOSTNAME is auto-included on Render.
121
122
 
122
123
  # SDK repro harness verbose HTTP logging
123
124
  MR_MAGIC_SDK_REPRO_HTTP_DEBUG=0 # Set to 1 for verbose HTTP previews in the SDK repro harness script
package/README.md CHANGED
@@ -99,6 +99,7 @@ MR_MAGIC_HTTP_TIMEOUT_MS=10000 # Optional, default 10000. Global outbound HTTP t
99
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
100
  MR_MAGIC_TOOL_ARG_CHUNK_SIZE=400 # Optional, default 400. Chunk size used when MR_MAGIC_LOG_TOOL_ARGS_CHUNKS is enabled.
101
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.
102
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.
103
104
  UPSTASH_REDIS_REST_URL= # Get from https://console.upstash.com/redis/rest, required if MR_MAGIC_EXPORT_BACKEND=redis
104
105
  UPSTASH_REDIS_REST_TOKEN= # Get from https://console.upstash.com/redis/rest, required if MR_MAGIC_EXPORT_BACKEND=redis
@@ -147,7 +148,9 @@ AIRTABLE_PERSONAL_ACCESS_TOKEN= # Required for push_catalog_to_airtable tool. Ge
147
148
  - **PORT** overrides both HTTP entrypoints when your platform injects one
148
149
  (Render, Fly, etc.). If unset, the MCP HTTP transport binds to `3444` and
149
150
  the JSON HTTP automation server binds to `3333`. CLI flags such as
150
- `mrmagic-cli server --port 4000` always take precedence.
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.
151
154
  - **MR_MAGIC_DOWNLOAD_BASE_URL** should match the public URL that exposes the
152
155
  `/downloads` routes. Include `:port` only when the HTTP server isn’t using
153
156
  the default for its protocol.
@@ -170,6 +173,11 @@ AIRTABLE_PERSONAL_ACCESS_TOKEN= # Required for push_catalog_to_airtable tool. Ge
170
173
  - **MR_MAGIC_MCP_HTTP_DIAGNOSTICS** (default `0`) enables detailed request
171
174
  metadata logging at the Streamable HTTP transport boundary (method, content
172
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`.
173
181
  - **MR_MAGIC_SDK_REPRO_HTTP_DEBUG** (default `0`) enables HTTP-level debugging
174
182
  output in `scripts/mcp-arg-boundary-sdk-repro.mjs` when validating argument
175
183
  boundary behavior from the SDK client path.
@@ -316,6 +324,40 @@ npm run server:http # or server:mcp / server:mcp:http
316
324
  Use a process manager (systemd, PM2, Docker CMD, etc.) to keep long-lived
317
325
  servers running.
318
326
 
327
+ ### Deploying on Render
328
+
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:
332
+
333
+ | Variable | Set by Render | Value |
334
+ |----------|--------------|-------|
335
+ | `RENDER` | ✅ always | `"true"` |
336
+ | `PORT` | ✅ always | `10000` (default, overridable in dashboard) |
337
+
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.
342
+
343
+ Recommended Render service settings:
344
+
345
+ - **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
349
+ - **Health Check Path:** `/health` (returns `{ "status": "ok", "providers": [...] }`)
350
+
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
+
319
361
  - **MCP server (Stdio)** for local Model Context Protocol clients (use the
320
362
  bundled CLI: `npm run server:mcp` or call `node ./src/bin/mcp-server.js`).
321
363
  - **MCP server (Streamable HTTP)** for remote MCP clients that speak the Streamable HTTP
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mr-magic-mcp-server",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Lyrics MCP server connecting LRCLIB, Genius, Musixmatch, and Melon",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -32,11 +32,47 @@ async function handleAction(action, track, actionOptions) {
32
32
 
33
33
  export function startHttpServer(options = {}) {
34
34
  const logger = createLogger('http-server');
35
- const host = options.remote ? '0.0.0.0' : options.host || '127.0.0.1';
36
- const port = Number(options.port) || 3333;
35
+ const host = options.remote
36
+ ? '0.0.0.0'
37
+ : (options.host || process.env.HOST || (process.env.RENDER ? '0.0.0.0' : '127.0.0.1'));
38
+ const port = Number(options.port) || Number(process.env.PORT) || 3333;
39
+
40
+ // When binding to 0.0.0.0, build an allowed-host set for DNS rebinding protection.
41
+ // Render sets RENDER_EXTERNAL_HOSTNAME automatically; add custom domains via
42
+ // MR_MAGIC_ALLOWED_HOSTS (comma-separated).
43
+ const allowedHosts =
44
+ host === '0.0.0.0'
45
+ ? new Set([
46
+ 'localhost',
47
+ '127.0.0.1',
48
+ ...(process.env.RENDER_EXTERNAL_HOSTNAME
49
+ ? [process.env.RENDER_EXTERNAL_HOSTNAME]
50
+ : []),
51
+ ...(process.env.MR_MAGIC_ALLOWED_HOSTS
52
+ ? process.env.MR_MAGIC_ALLOWED_HOSTS.split(',')
53
+ .map((h) => h.trim())
54
+ .filter(Boolean)
55
+ : [])
56
+ ])
57
+ : null;
37
58
 
38
59
  return new Promise((resolve) => {
39
60
  const server = http.createServer(async (req, res) => {
61
+ // DNS rebinding protection: validate Host header when binding to all interfaces.
62
+ if (allowedHosts) {
63
+ const reqHostname = (req.headers.host || '').split(':')[0].toLowerCase();
64
+ if (!reqHostname || !allowedHosts.has(reqHostname)) {
65
+ logger.warn('DNS rebinding protection: rejected request with disallowed Host', {
66
+ host: req.headers.host,
67
+ url: req.url,
68
+ method: req.method
69
+ });
70
+ res.writeHead(403, { 'Content-Type': 'application/json' });
71
+ res.end(JSON.stringify({ error: 'Forbidden: Host header not allowed' }));
72
+ return;
73
+ }
74
+ }
75
+
40
76
  if (req.method === 'GET' && req.url === '/health') {
41
77
  res.writeHead(200, { 'Content-Type': 'application/json' });
42
78
  res.end(
@@ -64,8 +64,10 @@ export async function startMcpHttpServer(options = {}) {
64
64
  const logger = createLogger('mcp-http-server');
65
65
  const httpDiagnostics = process.env.MR_MAGIC_MCP_HTTP_DIAGNOSTICS === '1';
66
66
  const configuredSessionless = Boolean(options.sessionless);
67
- const host = options.remote ? '0.0.0.0' : options.host || '127.0.0.1';
68
- const port = Number(options.port) || 3444;
67
+ const host = options.remote
68
+ ? '0.0.0.0'
69
+ : (options.host || process.env.HOST || (process.env.RENDER ? '0.0.0.0' : '127.0.0.1'));
70
+ const port = Number(options.port) || Number(process.env.PORT) || 3444;
69
71
 
70
72
  const server = new Server(
71
73
  { name: 'mcp-http-server', version: '0.1.4' },
@@ -91,7 +93,24 @@ export async function startMcpHttpServer(options = {}) {
91
93
  await server.connect(transport);
92
94
  await logTokenStatus({ context: 'http-mcp' });
93
95
 
94
- const app = createMcpExpressApp({ host });
96
+ // When binding to 0.0.0.0 the SDK requires an explicit allowedHosts list for
97
+ // DNS rebinding protection. Build it from well-known safe hosts plus any
98
+ // platform-injected hostname (Render sets RENDER_EXTERNAL_HOSTNAME automatically).
99
+ const allowedHosts =
100
+ host === '0.0.0.0'
101
+ ? [
102
+ 'localhost',
103
+ '127.0.0.1',
104
+ ...(process.env.RENDER_EXTERNAL_HOSTNAME
105
+ ? [process.env.RENDER_EXTERNAL_HOSTNAME]
106
+ : []),
107
+ ...(process.env.MR_MAGIC_ALLOWED_HOSTS
108
+ ? process.env.MR_MAGIC_ALLOWED_HOSTS.split(',').map((h) => h.trim()).filter(Boolean)
109
+ : [])
110
+ ]
111
+ : undefined;
112
+
113
+ const app = createMcpExpressApp({ host, ...(allowedHosts ? { allowedHosts } : {}) });
95
114
  app.get('/health', async (_req, res) => {
96
115
  res.json({ status: 'ok', providers: await getProviderStatus() });
97
116
  });
@@ -199,7 +218,7 @@ export async function startMcpHttpServer(options = {}) {
199
218
  });
200
219
  }
201
220
 
202
- if (process.argv[1]?.endsWith('mcp-http-server.js')) {
221
+ if (process.argv[1]?.endsWith('transport/mcp-http-server.js')) {
203
222
  startMcpHttpServer().catch((error) => {
204
223
  console.error(error);
205
224
  process.exit(1);