mr-magic-mcp-server 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mr-magic-mcp-server",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Lyrics MCP server connecting LRCLIB, Genius, Musixmatch, and Melon",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -84,12 +84,25 @@ export function startHttpServer(options = {}) {
84
84
 
85
85
  if (req.method === 'GET' && req.url.startsWith('/downloads/')) {
86
86
  const segments = req.url.split('/');
87
- const [, , downloadId, ...rest] = segments;
88
- // Strip trailing filename segment (e.g. "artist-song.srt") when present so the
89
- // Redis key lookup uses only the extension portion of the path.
90
- const hasFilename = rest.length > 1 && rest[rest.length - 1].includes('.');
91
- const extensionParts = hasFilename ? rest.slice(0, -1) : rest;
92
- const extension = extensionParts.join('/') || '';
87
+ const [, , idSegment, ...rest] = segments;
88
+
89
+ // Support two URL formats:
90
+ // flat: /downloads/export-xxx.srt → id=export-xxx, extension=srt
91
+ // compound: /downloads/export-xxx/romanized.srt → id=export-xxx, extension=romanized.srt
92
+ let downloadId, extension;
93
+ if (rest.length === 0 && idSegment && idSegment.includes('.')) {
94
+ // Flat format – extension is appended to the id with a dot.
95
+ const dotIdx = idSegment.lastIndexOf('.');
96
+ downloadId = idSegment.slice(0, dotIdx);
97
+ extension = idSegment.slice(dotIdx + 1);
98
+ } else {
99
+ // Compound / path-segment format.
100
+ downloadId = idSegment;
101
+ // Strip trailing human-readable filename (e.g. "artist-song.srt") when present.
102
+ const hasFilename = rest.length > 1 && rest[rest.length - 1].includes('.');
103
+ const extensionParts = hasFilename ? rest.slice(0, -1) : rest;
104
+ extension = extensionParts.join('/') || '';
105
+ }
93
106
  if (!downloadId || !extension) {
94
107
  logger.warn('Invalid download path', {
95
108
  context: 'http-download',
@@ -134,8 +134,8 @@ export async function startMcpHttpServer(options = {}) {
134
134
  });
135
135
 
136
136
  // ── Export download endpoint ──────────────────────────────────────────────────
137
- app.get('/downloads/:downloadId/:extension', async (req, res) => {
138
- const { downloadId, extension } = req.params;
137
+ // Helper shared by both download route handlers.
138
+ async function serveDownload(downloadId, extension, res, url) {
139
139
  if (!downloadId || !extension) {
140
140
  res.status(400).json({ error: 'Invalid download path' });
141
141
  return;
@@ -163,9 +163,28 @@ export async function startMcpHttpServer(options = {}) {
163
163
  bytes: Buffer.byteLength(content)
164
164
  });
165
165
  } catch (error) {
166
- logger.error('Download lookup failed', { error, url: req.originalUrl });
166
+ logger.error('Download lookup failed', { error, url });
167
167
  res.status(500).json({ error: 'Failed to fetch export' });
168
168
  }
169
+ }
170
+
171
+ // Flat format: /downloads/export-xxx.srt → id=export-xxx, extension=srt
172
+ app.get('/downloads/:idWithExt', async (req, res) => {
173
+ const { idWithExt } = req.params;
174
+ if (!idWithExt || !idWithExt.includes('.')) {
175
+ res.status(400).json({ error: 'Invalid download path' });
176
+ return;
177
+ }
178
+ const dotIdx = idWithExt.lastIndexOf('.');
179
+ const downloadId = idWithExt.slice(0, dotIdx);
180
+ const extension = idWithExt.slice(dotIdx + 1);
181
+ await serveDownload(downloadId, extension, res, req.originalUrl);
182
+ });
183
+
184
+ // Compound format: /downloads/export-xxx/romanized.srt → id=export-xxx, extension=romanized.srt
185
+ app.get('/downloads/:downloadId/:extension', async (req, res) => {
186
+ const { downloadId, extension } = req.params;
187
+ await serveDownload(downloadId, extension, res, req.originalUrl);
169
188
  });
170
189
 
171
190
  // ── Streamable HTTP transport (/mcp) ──────────────────────────────────────────
@@ -30,8 +30,11 @@ export default class RedisStorage {
30
30
 
31
31
  const expiresAt = new Date(Date.now() + this.ttl * 1000).toISOString();
32
32
  const base = this.downloadBaseUrl?.replace(/[\/]+$/, '') || '';
33
- const bareExt = extension.includes('.') ? extension.split('.').pop() : extension;
34
- const url = `${base}/downloads/${id}/${extension}/${baseName}.${bareExt}`;
33
+ // Compound extensions (e.g. "romanized.srt") become a path segment;
34
+ // simple extensions (e.g. "srt") are appended directly to the id.
35
+ const url = extension.includes('.')
36
+ ? `${base}/downloads/${id}/${extension}`
37
+ : `${base}/downloads/${id}.${extension}`;
35
38
  return new ExportStorageResult({ url, expiresAt, skipped: false });
36
39
  }
37
40
  }