@eldment/meting-mcp 1.6.2 → 1.6.4

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Meting-MCP
2
2
 
3
- `Meting-MCP` 是基于 **[metowolf/Meting](https://github.com/metowolf/Meting)** 构建的 MCP Server,支持 `netease`、`tencent`、`kugou`、`baidu`、`kuwo` 等音乐平台,提供搜索、歌曲、专辑、歌手、歌单、播放链接、歌词、封面等能力
3
+ `Meting-MCP` 是基于 **[metowolf/Meting](https://github.com/metowolf/Meting)** 构建的 MCP Server,支持 [网易云音乐](https://music.163.com/)(`netease`)、[腾讯音乐](https://y.qq.com/)(`tencent`)、[酷狗音乐](https://www.kugou.com/)(`kugou`)、[千千音乐](https://music.taihe.com/)(`baidu`)、[酷我音乐](https://www.kuwo.cn/)(`kuwo`) 等音乐平台,提供搜索、歌曲、专辑、歌手、歌单、播放链接、歌词、封面等能力
4
4
 
5
5
  ## 提供的 MCP 工具
6
6
 
@@ -23,11 +23,11 @@ Claude 示例配置:
23
23
  "mcpServers": {
24
24
  "meting": {
25
25
  "command": "npx",
26
- "args": [
27
- "-y",
28
- "@eldment/meting-mcp@latest"
29
- ],
30
- "env": {},
26
+ "args": ["-y", "@eldment/meting-mcp@latest"],
27
+ "env": {
28
+ "METING_NETEASE_COOKIE": "...",
29
+ "METING_TENCENT_COOKIE": "..."
30
+ },
31
31
  "timeout": 60000
32
32
  }
33
33
  }
@@ -44,11 +44,33 @@ args = [
44
44
  "-y",
45
45
  "@eldment/meting-mcp@latest",
46
46
  ]
47
- env = {}
47
+ env = {
48
+ METING_NETEASE_COOKIE = "...",
49
+ METING_TENCENT_COOKIE = "...",
50
+ }
48
51
  tool_timeout_sec = 60
49
52
  disabled = false
50
53
  ```
51
54
 
55
+ ## Cookie 配置
56
+
57
+ MCP 运行时会优先从环境变量读取 cookie,再回退到工具输入参数传入的 cookie,优先级如下:
58
+
59
+ 1. `METING_<PLATFORM>_COOKIE`
60
+ 2. `METING_COOKIE`
61
+ 3. MCP 工具调用时传入的 `cookie`
62
+
63
+ 当前支持的环境变量:
64
+
65
+ - `METING_NETEASE_COOKIE`
66
+ - `METING_TENCENT_COOKIE`
67
+ - `METING_KUGOU_COOKIE`
68
+ - `METING_BAIDU_COOKIE`
69
+ - `METING_KUWO_COOKIE`
70
+ - `METING_COOKIE`(兜底变量)
71
+
72
+ 如果只需要给某一个平台带 cookie,优先使用对应的平台变量;如果想统一兜底,可以只设置 `METING_COOKIE`
73
+
52
74
  ---
53
75
 
54
76
  关键词: MCP Server | Model Context Protocol | Music API | Node.js MCP | AI Tool Integration | NetEase Cloud Music | Tencent QQ Music | KuGou Music | Baidu Music | Kuwo Music | Lyrics API | Playlist API
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eldment/meting-mcp",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
4
4
  "description": "MCP server wrapper for the Meting multi-platform music API core.",
5
5
  "type": "module",
6
6
  "main": "./src/mcp-server.js",
@@ -17,6 +17,8 @@
17
17
  "scripts": {
18
18
  "start": "node src/index.js",
19
19
  "lint": "node --check src/index.js && node --check src/mcp-server.js && node --check src/meting.js && node --check src/providers/index.js && node --check test/test.js && node --check test/example.js",
20
+ "format": "prettier --write .",
21
+ "format:check": "prettier --check .",
20
22
  "test": "node test/test.js",
21
23
  "example": "node test/example.js",
22
24
  "build": "npm pack --dry-run",
@@ -59,5 +61,8 @@
59
61
  "dependencies": {
60
62
  "@modelcontextprotocol/sdk": "^1.27.1",
61
63
  "zod": "^4.3.6"
64
+ },
65
+ "devDependencies": {
66
+ "prettier": "^3.6.2"
62
67
  }
63
68
  }
package/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { CreateMcpServer, serviceMetadata } from "./mcp-server.js";
@@ -12,7 +12,7 @@ function GetHelpText() {
12
12
  "Usage:",
13
13
  " meting-mcp Start the MCP stdio server",
14
14
  " meting-mcp --help Show help",
15
- " meting-mcp --version Show version"
15
+ " meting-mcp --version Show version",
16
16
  ].join("\n");
17
17
  }
18
18
 
@@ -47,7 +47,9 @@ async function Main() {
47
47
  await StartServer();
48
48
  }
49
49
 
50
- Main().catch(error => {
51
- process.stderr.write(`meting-mcp failed to start: ${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
50
+ Main().catch((error) => {
51
+ process.stderr.write(
52
+ `meting-mcp failed to start: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`
53
+ );
52
54
  process.exit(1);
53
55
  });
package/src/mcp-server.js CHANGED
@@ -1,10 +1,20 @@
1
+ import { createRequire } from "module";
1
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
3
  import * as z from "zod/v4";
3
4
  import Meting from "./meting.js";
4
5
 
6
+ const require = createRequire(import.meta.url);
7
+ const { name: packageName, version } = require("../package.json");
8
+
9
+ const packageUrl = `https://www.npmjs.com/package/${packageName}`;
10
+
5
11
  export const serviceMetadata = Object.freeze({
6
12
  name: "meting-mcp",
7
- version: "1.6.1"
13
+ version,
14
+ title: "Meting MCP",
15
+ description:
16
+ "Search music and fetch song, album, artist, playlist, lyrics, cover, and play URL data across NetEase, Tencent, KuGou, Baidu, and Kuwo.",
17
+ websiteUrl: packageUrl,
8
18
  });
9
19
 
10
20
  const platformCatalog = Object.freeze([
@@ -12,15 +22,36 @@ const platformCatalog = Object.freeze([
12
22
  { name: "Tencent Music", code: "tencent" },
13
23
  { name: "KuGou Music", code: "kugou" },
14
24
  { name: "Baidu Music", code: "baidu" },
15
- { name: "Kuwo Music", code: "kuwo" }
25
+ { name: "Kuwo Music", code: "kuwo" },
16
26
  ]);
17
27
 
18
28
  const platformSchema = z.enum(Meting.getSupportedPlatforms());
19
29
 
20
- function CreateClient(platform, cookie) {
30
+ const cookieEnvNames = Object.freeze({
31
+ netease: "METING_NETEASE_COOKIE",
32
+ tencent: "METING_TENCENT_COOKIE",
33
+ kugou: "METING_KUGOU_COOKIE",
34
+ baidu: "METING_BAIDU_COOKIE",
35
+ kuwo: "METING_KUWO_COOKIE",
36
+ });
37
+
38
+ function ReadEnvValue(name) {
39
+ const value = process.env[name];
40
+ return typeof value === "string" && value.trim() !== "" ? value : undefined;
41
+ }
42
+
43
+ export function ResolveCookie(platform, inputCookie) {
44
+ const platformCookie = ReadEnvValue(cookieEnvNames[platform]);
45
+ const fallbackCookie = ReadEnvValue("METING_COOKIE");
46
+ return platformCookie ?? fallbackCookie ?? inputCookie;
47
+ }
48
+
49
+ function CreateClient(platform, inputCookie) {
21
50
  const meting = new Meting(platform);
22
51
  meting.format(true);
23
52
 
53
+ const cookie = ResolveCookie(platform, inputCookie);
54
+
24
55
  if (cookie) {
25
56
  meting.cookie(cookie);
26
57
  }
@@ -46,9 +77,9 @@ function CreateTextResult(result, isError = false) {
46
77
  content: [
47
78
  {
48
79
  type: "text",
49
- text: JSON.stringify(result, null, 2)
50
- }
51
- ]
80
+ text: JSON.stringify(result, null, 2),
81
+ },
82
+ ],
52
83
  };
53
84
  }
54
85
 
@@ -57,7 +88,7 @@ function CreateOperationResult(operation, platform, data) {
57
88
  ok: true,
58
89
  operation,
59
90
  platform,
60
- data
91
+ data,
61
92
  };
62
93
  }
63
94
 
@@ -66,27 +97,32 @@ function CreateOperationError(operation, platform, error) {
66
97
  ok: false,
67
98
  operation,
68
99
  platform,
69
- message: error instanceof Error ? error.message : String(error)
100
+ message: error instanceof Error ? error.message : String(error),
70
101
  };
71
102
  }
72
103
 
73
104
  function WithCommonInput(extraSchema) {
74
105
  return {
75
106
  platform: platformSchema.describe("Music platform code."),
76
- cookie: z.string().optional().describe("Optional platform cookie."),
77
- ...extraSchema
107
+ cookie: z
108
+ .string()
109
+ .optional()
110
+ .describe(
111
+ "Optional platform cookie. Lower priority than METING_<PLATFORM>_COOKIE and METING_COOKIE."
112
+ ),
113
+ ...extraSchema,
78
114
  };
79
115
  }
80
116
 
81
- function RegisterTool(server, toolName, description, inputSchema, handler) {
117
+ function RegisterTool(server, toolName, title, description, inputSchema, handler) {
82
118
  server.registerTool(
83
119
  toolName,
84
120
  {
85
- title: toolName,
121
+ title,
86
122
  description,
87
- inputSchema
123
+ inputSchema,
88
124
  },
89
- async input => {
125
+ async (input) => {
90
126
  try {
91
127
  const data = await handler(input);
92
128
  return CreateTextResult(CreateOperationResult(toolName, input.platform, data));
@@ -103,13 +139,13 @@ export function CreateMcpServer() {
103
139
  server.registerTool(
104
140
  "platforms",
105
141
  {
106
- title: "platforms",
107
- description: "List supported music platforms."
142
+ title: "List Supported Platforms",
143
+ description: "List supported music platforms.",
108
144
  },
109
145
  async () => {
110
146
  return CreateTextResult({
111
147
  ok: true,
112
- data: platformCatalog
148
+ data: platformCatalog,
113
149
  });
114
150
  }
115
151
  );
@@ -117,14 +153,20 @@ export function CreateMcpServer() {
117
153
  RegisterTool(
118
154
  server,
119
155
  "search",
156
+ "Search Music",
120
157
  "Search songs, albums or artists on a specific platform.",
121
158
  WithCommonInput({
122
159
  keyword: z.string().min(1).describe("Search keyword."),
123
- type: z.number().int().positive().optional().describe("Optional platform-specific search type."),
160
+ type: z
161
+ .number()
162
+ .int()
163
+ .positive()
164
+ .optional()
165
+ .describe("Optional platform-specific search type."),
124
166
  page: z.number().int().positive().optional().describe("Optional page number."),
125
- limit: z.number().int().positive().max(100).optional().describe("Optional page size.")
167
+ limit: z.number().int().positive().max(100).optional().describe("Optional page size."),
126
168
  }),
127
- async input => {
169
+ async (input) => {
128
170
  const meting = CreateClient(input.platform, input.cookie);
129
171
  const options = {};
130
172
 
@@ -147,11 +189,12 @@ export function CreateMcpServer() {
147
189
  RegisterTool(
148
190
  server,
149
191
  "song",
192
+ "Get Song Details",
150
193
  "Get song detail by id.",
151
194
  WithCommonInput({
152
- id: z.string().min(1).describe("Song id.")
195
+ id: z.string().min(1).describe("Song id."),
153
196
  }),
154
- async input => {
197
+ async (input) => {
155
198
  const meting = CreateClient(input.platform, input.cookie);
156
199
  return ParseResult(await meting.song(input.id));
157
200
  }
@@ -160,11 +203,12 @@ export function CreateMcpServer() {
160
203
  RegisterTool(
161
204
  server,
162
205
  "album",
206
+ "Get Album Details",
163
207
  "Get album detail by id.",
164
208
  WithCommonInput({
165
- id: z.string().min(1).describe("Album id.")
209
+ id: z.string().min(1).describe("Album id."),
166
210
  }),
167
- async input => {
211
+ async (input) => {
168
212
  const meting = CreateClient(input.platform, input.cookie);
169
213
  return ParseResult(await meting.album(input.id));
170
214
  }
@@ -173,12 +217,13 @@ export function CreateMcpServer() {
173
217
  RegisterTool(
174
218
  server,
175
219
  "artist",
220
+ "Get Artist Works",
176
221
  "Get artist songs by id.",
177
222
  WithCommonInput({
178
223
  id: z.string().min(1).describe("Artist id."),
179
- limit: z.number().int().positive().max(200).optional().describe("Optional result size.")
224
+ limit: z.number().int().positive().max(200).optional().describe("Optional result size."),
180
225
  }),
181
- async input => {
226
+ async (input) => {
182
227
  const meting = CreateClient(input.platform, input.cookie);
183
228
  return ParseResult(await meting.artist(input.id, input.limit));
184
229
  }
@@ -187,11 +232,12 @@ export function CreateMcpServer() {
187
232
  RegisterTool(
188
233
  server,
189
234
  "playlist",
235
+ "Get Playlist Details",
190
236
  "Get playlist detail by id.",
191
237
  WithCommonInput({
192
- id: z.string().min(1).describe("Playlist id.")
238
+ id: z.string().min(1).describe("Playlist id."),
193
239
  }),
194
- async input => {
240
+ async (input) => {
195
241
  const meting = CreateClient(input.platform, input.cookie);
196
242
  return ParseResult(await meting.playlist(input.id));
197
243
  }
@@ -200,12 +246,13 @@ export function CreateMcpServer() {
200
246
  RegisterTool(
201
247
  server,
202
248
  "url",
249
+ "Get Play URL",
203
250
  "Get playable song url by id.",
204
251
  WithCommonInput({
205
252
  id: z.string().min(1).describe("Song id."),
206
- br: z.number().int().positive().optional().describe("Optional bitrate.")
253
+ br: z.number().int().positive().optional().describe("Optional bitrate."),
207
254
  }),
208
- async input => {
255
+ async (input) => {
209
256
  const meting = CreateClient(input.platform, input.cookie);
210
257
  return ParseResult(await meting.url(input.id, input.br));
211
258
  }
@@ -214,11 +261,12 @@ export function CreateMcpServer() {
214
261
  RegisterTool(
215
262
  server,
216
263
  "lyric",
264
+ "Get Lyrics",
217
265
  "Get song lyric by id.",
218
266
  WithCommonInput({
219
- id: z.string().min(1).describe("Song id.")
267
+ id: z.string().min(1).describe("Song id."),
220
268
  }),
221
- async input => {
269
+ async (input) => {
222
270
  const meting = CreateClient(input.platform, input.cookie);
223
271
  return ParseResult(await meting.lyric(input.id));
224
272
  }
@@ -227,12 +275,13 @@ export function CreateMcpServer() {
227
275
  RegisterTool(
228
276
  server,
229
277
  "pic",
278
+ "Get Cover Image",
230
279
  "Get cover or picture url by id.",
231
280
  WithCommonInput({
232
281
  id: z.string().min(1).describe("Picture id."),
233
- size: z.number().int().positive().optional().describe("Optional picture size.")
282
+ size: z.number().int().positive().optional().describe("Optional picture size."),
234
283
  }),
235
- async input => {
284
+ async (input) => {
236
285
  const meting = CreateClient(input.platform, input.cookie);
237
286
  return ParseResult(await meting.pic(input.id, input.size));
238
287
  }
package/src/meting.js CHANGED
@@ -7,12 +7,12 @@
7
7
  * Released under the MIT license
8
8
  */
9
9
 
10
- import { URLSearchParams } from 'url';
11
- import ProviderFactory from './providers/index.js';
10
+ import { URLSearchParams } from "url";
11
+ import ProviderFactory from "./providers/index.js";
12
12
 
13
13
  class Meting {
14
- constructor(server = 'netease') {
15
- this.VERSION = '__VERSION__'; // 在构建时由 rollup 替换为实际版本号
14
+ constructor(server = "netease") {
15
+ this.VERSION = "__VERSION__"; // 在构建时由 rollup 替换为实际版本号
16
16
  this.raw = null;
17
17
  this.info = null;
18
18
  this.error = null;
@@ -30,7 +30,7 @@ class Meting {
30
30
  // 设置音乐平台
31
31
  site(server) {
32
32
  if (!ProviderFactory.isSupported(server)) {
33
- server = 'netease'; // 默认使用网易云音乐
33
+ server = "netease"; // 默认使用网易云音乐
34
34
  }
35
35
 
36
36
  this.server = server;
@@ -42,7 +42,7 @@ class Meting {
42
42
 
43
43
  // 设置 Cookie
44
44
  cookie(cookie) {
45
- this.header['Cookie'] = cookie;
45
+ this.header["Cookie"] = cookie;
46
46
  return this;
47
47
  }
48
48
 
@@ -61,15 +61,15 @@ class Meting {
61
61
  // HTTP 请求方法 - 使用 fetch API
62
62
  async _curl(url, payload = null, headerOnly = false) {
63
63
  const requestOptions = {
64
- method: payload ? 'POST' : 'GET',
65
- headers: { ...this.header }
64
+ method: payload ? "POST" : "GET",
65
+ headers: { ...this.header },
66
66
  };
67
67
 
68
68
  // 处理请求体
69
69
  if (payload) {
70
- if (typeof payload === 'object' && !Buffer.isBuffer(payload) && typeof payload !== 'string') {
70
+ if (typeof payload === "object" && !Buffer.isBuffer(payload) && typeof payload !== "string") {
71
71
  payload = new URLSearchParams(payload).toString();
72
- requestOptions.headers['Content-Type'] = 'application/x-www-form-urlencoded';
72
+ requestOptions.headers["Content-Type"] = "application/x-www-form-urlencoded";
73
73
  }
74
74
  requestOptions.body = payload;
75
75
  }
@@ -83,38 +83,38 @@ class Meting {
83
83
  const makeRequest = async () => {
84
84
  try {
85
85
  const response = await fetch(url, requestOptions);
86
-
86
+
87
87
  clearTimeout(timeoutId);
88
-
88
+
89
89
  // 存储响应信息
90
90
  this.info = {
91
91
  statusCode: response.status,
92
- headers: Object.fromEntries(response.headers.entries())
92
+ headers: Object.fromEntries(response.headers.entries()),
93
93
  };
94
94
 
95
95
  // 获取响应数据
96
96
  const data = await response.text();
97
97
  this.raw = data;
98
98
  this.error = null;
99
- this.status = '';
100
-
99
+ this.status = "";
100
+
101
101
  return this;
102
102
  } catch (err) {
103
103
  clearTimeout(timeoutId);
104
-
104
+
105
105
  // 处理错误
106
- if (err.name === 'AbortError') {
107
- this.error = 'TIMEOUT';
108
- this.status = 'Request timeout';
106
+ if (err.name === "AbortError") {
107
+ this.error = "TIMEOUT";
108
+ this.status = "Request timeout";
109
109
  } else {
110
110
  this.error = err.code || err.name;
111
111
  this.status = err.message;
112
112
  }
113
-
113
+
114
114
  // 重试机制
115
115
  if (retries > 0) {
116
116
  retries--;
117
- await new Promise(resolve => setTimeout(resolve, 1000));
117
+ await new Promise((resolve) => setTimeout(resolve, 1000));
118
118
  return makeRequest();
119
119
  } else {
120
120
  return this;
@@ -125,7 +125,6 @@ class Meting {
125
125
  return await makeRequest();
126
126
  }
127
127
 
128
-
129
128
  // ========== 公共 API 方法 ==========
130
129
 
131
130
  // 搜索功能