mr-magic-mcp-server 0.5.0 → 0.5.1
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 +7 -7
- package/src/providers/genius.js +23 -17
- package/src/providers/melon.js +22 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mr-magic-mcp-server",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Lyrics MCP server connecting LRCLIB, Genius, Musixmatch, and Melon",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -64,8 +64,10 @@
|
|
|
64
64
|
"@dotenvx/dotenvx": "^1.65.0",
|
|
65
65
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
66
66
|
"axios": "^1.16.0",
|
|
67
|
-
"
|
|
68
|
-
"
|
|
67
|
+
"commander": "^14.0.3",
|
|
68
|
+
"css-select": "^6.0.0",
|
|
69
|
+
"domutils": "^3.2.2",
|
|
70
|
+
"htmlparser2": "^10.1.0"
|
|
69
71
|
},
|
|
70
72
|
"devDependencies": {
|
|
71
73
|
"eslint": "^10.3.0",
|
|
@@ -85,8 +87,7 @@
|
|
|
85
87
|
"hasown": "npm:@socketregistry/hasown@^1",
|
|
86
88
|
"object-assign": "npm:@socketregistry/object-assign@^1",
|
|
87
89
|
"safer-buffer": "npm:@socketregistry/safer-buffer@^1",
|
|
88
|
-
"side-channel": "npm:@socketregistry/side-channel@^1"
|
|
89
|
-
"encoding-sniffer": "^1.0.2"
|
|
90
|
+
"side-channel": "npm:@socketregistry/side-channel@^1"
|
|
90
91
|
},
|
|
91
92
|
"resolutions": {
|
|
92
93
|
"entities": "^7.0.1",
|
|
@@ -99,7 +100,6 @@
|
|
|
99
100
|
"hasown": "npm:@socketregistry/hasown@^1",
|
|
100
101
|
"object-assign": "npm:@socketregistry/object-assign@^1",
|
|
101
102
|
"safer-buffer": "npm:@socketregistry/safer-buffer@^1",
|
|
102
|
-
"side-channel": "npm:@socketregistry/side-channel@^1"
|
|
103
|
-
"encoding-sniffer": "^1.0.2"
|
|
103
|
+
"side-channel": "npm:@socketregistry/side-channel@^1"
|
|
104
104
|
}
|
|
105
105
|
}
|
package/src/providers/genius.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
|
-
import
|
|
2
|
+
import { selectAll } from 'css-select';
|
|
3
|
+
import * as DomUtils from 'domutils';
|
|
4
|
+
import { parseDocument } from 'htmlparser2';
|
|
3
5
|
|
|
4
6
|
import { normalizeLyricRecord } from '../provider-result-schema.js';
|
|
5
7
|
import { assertEnv, getEnvValue } from '../utils/config.js';
|
|
@@ -119,7 +121,7 @@ export async function fetchFromGenius(track) {
|
|
|
119
121
|
return primary;
|
|
120
122
|
}
|
|
121
123
|
|
|
122
|
-
function normalizeNodeText(
|
|
124
|
+
function normalizeNodeText(node) {
|
|
123
125
|
const lines = [];
|
|
124
126
|
const traverse = (node) => {
|
|
125
127
|
if (!node) return;
|
|
@@ -145,7 +147,7 @@ function normalizeNodeText($node) {
|
|
|
145
147
|
}
|
|
146
148
|
};
|
|
147
149
|
|
|
148
|
-
traverse(
|
|
150
|
+
traverse(node);
|
|
149
151
|
|
|
150
152
|
return lines
|
|
151
153
|
.join(' ')
|
|
@@ -155,18 +157,19 @@ function normalizeNodeText($node) {
|
|
|
155
157
|
.trim();
|
|
156
158
|
}
|
|
157
159
|
|
|
158
|
-
function
|
|
160
|
+
function removeUnwantedNodes(node) {
|
|
161
|
+
const unwanted = selectAll(
|
|
162
|
+
'script,noscript,img,style,aside,.song_media_dropdown,.header_with_cover_art-primary_info',
|
|
163
|
+
node
|
|
164
|
+
);
|
|
165
|
+
unwanted.forEach((element) => DomUtils.removeElement(element));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function extractFromNodes(nodes) {
|
|
159
169
|
const blocks = [];
|
|
160
170
|
nodes.forEach((element) => {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
.find(
|
|
164
|
-
'script,noscript,img,style,aside,.song_media_dropdown,.header_with_cover_art-primary_info'
|
|
165
|
-
)
|
|
166
|
-
.remove()
|
|
167
|
-
.end();
|
|
168
|
-
|
|
169
|
-
const text = normalizeNodeText(cleaned);
|
|
171
|
+
removeUnwantedNodes(element);
|
|
172
|
+
const text = normalizeNodeText(element);
|
|
170
173
|
const stripped = stripSummaryText(text);
|
|
171
174
|
|
|
172
175
|
if (stripped) {
|
|
@@ -224,15 +227,18 @@ export async function fetchLyricsForGeniusSong(url) {
|
|
|
224
227
|
}
|
|
225
228
|
});
|
|
226
229
|
|
|
227
|
-
const
|
|
228
|
-
let blocks = extractFromNodes(
|
|
230
|
+
const document = parseDocument(response.data);
|
|
231
|
+
let blocks = extractFromNodes(selectAll('div[data-lyrics-container="true"]', document));
|
|
229
232
|
|
|
230
233
|
if (!blocks.length) {
|
|
231
|
-
blocks = extractFromNodes(
|
|
234
|
+
blocks = extractFromNodes(selectAll('div[class^="Lyrics__Container"]', document));
|
|
232
235
|
}
|
|
233
236
|
|
|
234
237
|
if (!blocks.length) {
|
|
235
|
-
const fallback =
|
|
238
|
+
const fallback = selectAll('.lyrics', document)
|
|
239
|
+
.map((node) => DomUtils.textContent(node))
|
|
240
|
+
.join('\n')
|
|
241
|
+
.trim();
|
|
236
242
|
if (fallback) {
|
|
237
243
|
blocks.push(fallback);
|
|
238
244
|
}
|
package/src/providers/melon.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
|
-
import
|
|
2
|
+
import { selectAll } from 'css-select';
|
|
3
|
+
import * as DomUtils from 'domutils';
|
|
4
|
+
import { parseDocument } from 'htmlparser2';
|
|
3
5
|
|
|
4
6
|
import { normalizeLyricRecord, recomputeSyncFlags } from '../provider-result-schema.js';
|
|
5
7
|
import { MELON_COOKIE, warnMissingEnv } from '../utils/config.js';
|
|
@@ -99,25 +101,30 @@ function extractSongId(value) {
|
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
function parseSearchPage(html) {
|
|
102
|
-
const
|
|
104
|
+
const document = parseDocument(html);
|
|
103
105
|
const seenIds = new Set();
|
|
104
|
-
return
|
|
105
|
-
.map((
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
return selectAll('#frm_defaultList > div > table > tbody > tr', document)
|
|
107
|
+
.map((row) => {
|
|
108
|
+
const cells = selectAll('td', row);
|
|
109
|
+
const titleAnchor = selectAll('a.fc_gray', cells[2] || [])[0];
|
|
110
|
+
const artistAnchor = selectAll('#artistName > a', cells[3] || [])[0];
|
|
111
|
+
const albumAnchor = selectAll('a', cells[4] || [])[0];
|
|
112
|
+
const titleHref =
|
|
113
|
+
DomUtils.getAttributeValue(titleAnchor, 'href') ||
|
|
114
|
+
DomUtils.getAttributeValue(titleAnchor, 'onclick') ||
|
|
115
|
+
'';
|
|
116
|
+
const artistHref =
|
|
117
|
+
DomUtils.getAttributeValue(artistAnchor, 'href') ||
|
|
118
|
+
DomUtils.getAttributeValue(artistAnchor, 'onclick') ||
|
|
119
|
+
'';
|
|
113
120
|
let songId = extractSongId(titleHref) || extractSongId(artistHref);
|
|
114
|
-
if (!songId) songId = extractSongId(
|
|
121
|
+
if (!songId) songId = extractSongId(DomUtils.getOuterHTML(row) || '');
|
|
115
122
|
if (!songId || seenIds.has(songId)) {
|
|
116
123
|
return null;
|
|
117
124
|
}
|
|
118
|
-
const title =
|
|
119
|
-
const artist =
|
|
120
|
-
const album =
|
|
125
|
+
const title = DomUtils.textContent(titleAnchor).trim().replace(/\s+/g, ' ');
|
|
126
|
+
const artist = DomUtils.textContent(artistAnchor).trim().replace(/\s+/g, ' ');
|
|
127
|
+
const album = DomUtils.textContent(albumAnchor).trim().replace(/\s+/g, ' ');
|
|
121
128
|
seenIds.add(songId);
|
|
122
129
|
if (!title && !artist) return null;
|
|
123
130
|
return { songId, title, artist, album };
|