ben-music-mcp 0.1.5__py3-none-any.whl

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.

Potentially problematic release.


This version of ben-music-mcp might be problematic. Click here for more details.

@@ -0,0 +1,233 @@
1
+
2
+
3
+
4
+
5
+ from fastmcp import FastMCP
6
+ from .tool import (
7
+ _hello,
8
+ _get_song_info,
9
+ _get_song_id,
10
+ _lyric,
11
+ _search_artist,
12
+ _login_anonymous,
13
+ _login_refresh,
14
+ _get_artist_info,
15
+ _get_artist_hot_songs,
16
+ _get_music_url,
17
+ _get_song_comment,
18
+ _get_playlist,
19
+ _get_toplist,
20
+ _get_similar_songs,
21
+ _get_style_songs,
22
+ _start_netease_music,
23
+ _get_uid,
24
+ _get_user_playlist,
25
+ _return_stylelist,
26
+ _return_toplist_name,
27
+ global_cookie
28
+ )
29
+
30
+ mcp = FastMCP("MCPService")
31
+
32
+
33
+ @mcp.tool
34
+ def hello() -> dict:
35
+ """测试使用,version=0.1.5"""
36
+ return _hello()
37
+
38
+
39
+
40
+
41
+
42
+ @mcp.tool
43
+ def get_song_info(keywords: str) -> dict:
44
+ """
45
+ 根据关键词搜索歌曲信息,歌曲id无需向用户透漏
46
+ :param keywords: 歌曲关键词
47
+ :return: 歌曲信息
48
+ """
49
+ return _get_song_info(keywords)
50
+
51
+ # @mcp.tool
52
+ # def get_song_id(keywords: str) -> dict:
53
+ # """
54
+ # 根据关键词搜索歌曲,返回第一首歌的ID
55
+ # :param keywords: 歌曲关键词
56
+ # :return: 歌曲ID
57
+ # """
58
+ # return _get_song_id(keywords)
59
+
60
+ @mcp.tool
61
+ def lyric(keywords: str) -> dict:
62
+ """
63
+ 根据歌名关键词搜索歌词
64
+ :param keywords: 歌曲关键词
65
+ :return: 歌词文本
66
+ """
67
+ return _lyric(keywords)
68
+
69
+ @mcp.tool
70
+ def get_music_url(keywords: str, level: str = "standard") -> dict:
71
+ """
72
+ 根据关键词获取音乐在线试听url
73
+ :param keywords: 歌曲关键词
74
+ :param level: 音质等级
75
+ :return: 音乐url json
76
+ """
77
+ return _get_music_url(keywords, level)
78
+
79
+ @mcp.tool
80
+ def get_song_comment(keywords: str) -> dict:
81
+ """
82
+ 根据歌名关键词获取歌曲评论
83
+ :param keywords: 歌曲关键词
84
+ :return: 歌曲评论json
85
+ """
86
+ return _get_song_comment(keywords)
87
+
88
+ @mcp.tool
89
+ def get_similar_songs(keywords: str) -> dict:
90
+ """
91
+ 根据关键词获取相似音乐(先查id再查/simi/song)
92
+ :param keywords: 歌曲关键词
93
+ :return: 相似音乐json
94
+ """
95
+ return _get_similar_songs(keywords)
96
+
97
+ @mcp.tool
98
+ def get_style_songs(style_name: str) -> dict:
99
+ """
100
+ 根据曲风名返回歌曲列表。
101
+ :param style_name: 曲风名
102
+ :return: 歌曲列表json
103
+ """
104
+ return _get_style_songs(style_name)
105
+
106
+
107
+
108
+
109
+
110
+ @mcp.tool
111
+ def search_artist(keyword: str) -> dict:
112
+ """
113
+ 根据关键词搜索歌手信息
114
+ :param keyword: 歌手关键词
115
+ :return: 歌手信息字符串
116
+ """
117
+ return _search_artist(keyword)
118
+
119
+ @mcp.tool
120
+ def get_artist_info(keyword: str) -> dict:
121
+ """
122
+ 根据关键词获取歌手详细信息
123
+ :param keyword: 歌手关键词
124
+ :return: 歌手详细信息json
125
+ """
126
+ return _get_artist_info(keyword)
127
+
128
+ @mcp.tool
129
+ def get_artist_hot_songs(keyword: str) -> dict:
130
+ """
131
+ 根据关键词获取歌手最火的50首歌曲
132
+ :param keyword: 歌手关键词
133
+ :return: 热门歌曲json
134
+ """
135
+ return _get_artist_hot_songs(keyword)
136
+
137
+
138
+
139
+
140
+
141
+ @mcp.tool
142
+ def get_playlist(id: int) -> dict:
143
+ """
144
+ 根据歌单id获取歌单详情
145
+ :param id: 歌单id
146
+ :return: 歌单详情json
147
+ """
148
+ return _get_playlist(id)
149
+
150
+ @mcp.tool
151
+ def get_toplist(name: str) -> dict:
152
+ """
153
+ 根据排行榜名字获取榜单详情(先查id再查_get_playlist)
154
+ :param name: 榜单名称
155
+ :return: 榜单详情json
156
+ """
157
+ return _get_toplist(name)
158
+
159
+
160
+
161
+
162
+
163
+ # @mcp.tool()
164
+ # def get_uid(nickname: str) -> dict:
165
+ # """
166
+ # 根据用户昵称获取其uid
167
+ # :param nickname: 用户昵称
168
+ # :return: 用户uid json
169
+ # """
170
+ # return _get_uid(nickname)
171
+
172
+ @mcp.tool()
173
+ def get_user_playlist(nickname: str) -> dict:
174
+ """
175
+ 输入用户昵称,获取用户歌单(含有歌单id)
176
+ :param nickname: 用户昵称
177
+ :return: 歌单列表json
178
+ """
179
+ return _get_user_playlist(nickname)
180
+
181
+
182
+
183
+
184
+
185
+ @mcp.tool
186
+ def return_stylelist() -> dict:
187
+ """
188
+ 返回所有风格名称(不含id),从styleList.json读取。没有输入参数
189
+ :return: {"code": 200, "msg": "success", "data": [风格名列表]}
190
+ """
191
+ return _return_stylelist()
192
+
193
+ @mcp.tool
194
+ def return_toplist_name() -> dict:
195
+ """
196
+ 返回所有排行榜名称(不含id),从toplist.json读取。没有输入参数
197
+ :return: {"code": 200, "msg": "success", "data": [榜单名列表]}
198
+ """
199
+ return _return_toplist_name()
200
+
201
+
202
+
203
+ # @mcp.tool
204
+ # def login_anonymous() -> dict:
205
+ # """
206
+ # 游客登录,获取游客 cookie,并保存到全局变量
207
+ # :return: 游客 cookie 字符串
208
+ # """
209
+ # return _login_anonymous()
210
+
211
+ # @mcp.tool
212
+ # def login_refresh() -> dict:
213
+ # """
214
+ # 刷新登录状态,获取新的 cookie
215
+ # :return: 新的 cookie 字符串
216
+ # """
217
+ # return _login_refresh()
218
+
219
+
220
+ @mcp.tool()
221
+ def start_netease_music(exe_path: str) -> dict:
222
+ """
223
+ 启动本地网易云音乐客户端
224
+ :param exe_path: 网易云音乐客户端的 exe 路径,默认 C:\\CloudMusic\\cloudmusic.exe
225
+ :return: 启动结果 json
226
+ """
227
+ return _start_netease_music(exe_path)
228
+
229
+ def main():
230
+ mcp.run(transport="stdio")
231
+
232
+ if __name__ == "__main__":
233
+ mcp.run(transport="stdio")
@@ -0,0 +1 @@
1
+ {"MUSIC_A_T": "1762674143805", "MUSIC_R_T": "0", "MUSIC_A": "00FD2909A8CC993C4FB0FC90128C7E2E3EEC34146238C0F67438D16B51DB026C9AC74700E4270BCF0954B0454EDA13126BBD915DF4CF52EF21ACB5141059BEFA897B59C94F25484D47A8C8B257C322CB9D745805D39E61449D59DF795D936D9F5F628CE6E0D6610792EB91B0D34EBFB837357B9AC0135C97B18B1D6FB1EA248B5532FEFF6A34A5C57B4EDE76C36AACF86A542A7358C44E0F2978A3B7AA852F2A84DA48857A5CAAE3FA2D4968232C727BFD96CD7D7E7479829C726EC940B1880EFD01740C665B517699FAAEF1CA3BDD92E049A36AB793A15AD9AD75E4D691D4D821E363CAE7BF3AC9586A653999810747C974239EC1E43944EB679B3DC92CFF113BDBC9279CCDCD5271C55DD18FE58847F6AF6D5E12D1D8BB8417D99B2AEDF13B4EC9097EE893ABD6BBC56E37187D2386D061DE4288B289AD7F4068E488B691682F61F170D1DDB0E6D84A679E49677A1E529C96FE9540E0B5527B1D23C956FEAB3CB6B3647B749B1CAE39EE70CC6FFC06E5F4AC33DA9470E856C1391FD7006FB487C9537E4D11B2D469A78615B32771CAE5A725D8FD8FA172E865A236F487B665817AE5F57F4AF8781F2663CA0E5780CBBF4474D616D068125C6EAFC1741C5B844F8BB6621037BA47FC0CD9E48FA63499730DF294E62E3A2CF99319D6DD778B573CCB7B99B2AFC4CC9035540E3864862E2707B4898BD831AF07C4F0774ED9AFA3F96B94079C3F48D262C4898BD0A3E39016", "__csrf": "eabbb81b921eb9637c0b389245b3a0dc", "MUSIC_R_U": "00F9CF8A93CB06AEC0A041DB4FC98922D18884F91B648A3F78CC43943902835DB940DC984AC33ED14A019B44DCDB707C2FA8F2ED50EC556FB93B675A320C20F5E39A3EA15488819D77A922244FB3B663B4"}
@@ -0,0 +1,5 @@
1
+ from . import mcp
2
+
3
+
4
+ if __name__ == "__main__":
5
+ mcp.run(transport="sse")
@@ -0,0 +1,26 @@
1
+ [{"tagId": 1000, "tagName": "流行"},
2
+ {"tagId": 1017, "tagName": "嘻哈说唱"},
3
+ {"tagId": 1015, "tagName": "电子"},
4
+ {"tagId": 1028, "tagName": "R&B"},
5
+ {"tagId": 1008, "tagName": "摇滚"},
6
+ {"tagId": 1003, "tagName": "民谣"},
7
+ {"tagId": 10119, "tagName": "国风"},
8
+ {"tagId": 1010, "tagName": "原声带"},
9
+ {"tagId": 1026, "tagName": "儿童"},
10
+ {"tagId": 79088, "tagName": "慢摇DJ"},
11
+ {"tagId": 1001, "tagName": "中国传统特色"},
12
+ {"tagId": 1022, "tagName": "古典"},
13
+ {"tagId": 1011, "tagName": "爵士"},
14
+ {"tagId": 1252, "tagName": "二次元"},
15
+ {"tagId": 1006, "tagName": "轻音乐"},
16
+ {"tagId": 1029, "tagName": "放克"},
17
+ {"tagId": 1263, "tagName": "世界乐器体裁"},
18
+ {"tagId": 1077, "tagName": "乡村"},
19
+ {"tagId": 1007, "tagName": "新世纪"},
20
+ {"tagId": 152124, "tagName": "朋克"},
21
+ {"tagId": 1071, "tagName": "拉丁音乐"},
22
+ {"tagId": 1041, "tagName": "金属"},
23
+ {"tagId": 1037, "tagName": "蓝调"},
24
+ {"tagId": 1014, "tagName": "另类/独立"},
25
+ {"tagId": 1043, "tagName": "雷鬼"},
26
+ {"tagId": 1012, "tagName": "世界音乐"}]
ben_music_mcp/tool.py ADDED
@@ -0,0 +1,519 @@
1
+
2
+ import subprocess
3
+ import requests
4
+
5
+ # 全局变量保存 cookie
6
+ global_cookie = {}
7
+
8
+ import os
9
+ import json
10
+
11
+ def _save_cookie_to_file(cookie: dict, filename: str = "cookie.txt"):
12
+ """将cookie保存到文件"""
13
+ with open(filename, "w", encoding="utf-8") as f:
14
+ f.write(json.dumps(cookie, ensure_ascii=False))
15
+
16
+ def _load_cookie_from_file(filename: str = "cookie.txt") -> dict:
17
+ """从文件读取cookie"""
18
+ if not os.path.exists(filename):
19
+ return {}
20
+ try:
21
+ with open(filename, "r", encoding="utf-8") as f:
22
+ return json.loads(f.read())
23
+ except Exception:
24
+ return {}
25
+
26
+ def _get_cookie() -> dict:
27
+ """获取当前cookie(优先文件)"""
28
+ global global_cookie
29
+ if global_cookie:
30
+ return global_cookie
31
+ cookie = _load_cookie_from_file()
32
+ global_cookie = cookie
33
+ return cookie
34
+
35
+ def _hello() -> dict:
36
+ """方法注释"""
37
+ return {"code": 200, "msg": "success", "data": "我是哈尔滨工业大学的学生,我叫张三,学号是123456。"}
38
+
39
+ def _get_song_info(keywords: str) -> dict:
40
+ """
41
+ 搜索歌曲,返回json格式
42
+ :param keywords: 歌曲关键词
43
+ :return: 歌曲信息json
44
+ """
45
+ url = f"https://ncm.nekogan.com/search?keywords={keywords}"
46
+ try:
47
+ resp = requests.get(url, timeout=5)
48
+ resp.raise_for_status()
49
+ data = resp.json()
50
+ if "result" in data and "songs" in data["result"]:
51
+ songs = data["result"]["songs"]
52
+ if not songs:
53
+ return {"code": 404, "msg": "未找到相关歌曲", "data": {}}
54
+ song = songs[0]
55
+ return {
56
+ "code": 200,
57
+ "msg": "success",
58
+ "data": {
59
+ "id": song.get("id", "未知"),
60
+ "name": song.get("name", "未知"),
61
+ "artists": [a["name"] for a in song.get("artists", [])],
62
+ "album": song.get("album", {}).get("name", "未知"),
63
+ "alias": song.get("alias", []),
64
+ "transNames": song.get("transNames", []),
65
+ "duration": song.get("duration", 0),
66
+ "fee": song.get("fee", 0),
67
+ "mvid": song.get("mvid", 0)
68
+ }
69
+ }
70
+ else:
71
+ return {"code": 404, "msg": "未找到相关歌曲", "data": {}}
72
+ except Exception as e:
73
+ return {"code": 500, "msg": f"请求失败: {e}", "data": {}}
74
+
75
+ def _get_song_id(keywords: str) -> dict:
76
+ """
77
+ 搜索歌曲,返回第一个歌曲ID,json格式
78
+ :param keywords: 歌曲关键词
79
+ :return: 歌曲ID json
80
+ """
81
+ result = _get_song_info(keywords)
82
+ if result.get("code") == 200 and "id" in result.get("data", {}):
83
+ return {"code": 200, "msg": "success", "id": str(result["data"]["id"])}
84
+ return {"code": 404, "msg": "未找到相关歌曲ID", "id": ""}
85
+
86
+ def _lyric(keywords: str) -> dict:
87
+ """先获取歌曲id,再查歌词,返回json"""
88
+ song_id_json = _get_song_id(keywords)
89
+ song_id = song_id_json.get("id", "")
90
+ if not song_id or not song_id.isdigit():
91
+ return {"code": 404, "msg": f"未找到歌曲ID,原因:{song_id_json}", "data": ""}
92
+ url = f"https://ncm.nekogan.com/lyric?id={song_id}"
93
+ try:
94
+ resp = requests.get(url, timeout=5)
95
+ resp.raise_for_status()
96
+ data = resp.json()
97
+ lyric_text = data.get("lrc", {}).get("lyric", "")
98
+ if lyric_text:
99
+ return {"code": 200, "msg": "success", "data": lyric_text}
100
+ else:
101
+ return {"code": 404, "msg": "未找到歌词。", "data": ""}
102
+ except Exception as e:
103
+ return {"code": 500, "msg": f"请求失败 {e}", "data": ""}
104
+
105
+
106
+ def _search_artist(keyword: str) -> dict:
107
+ """
108
+ 搜索歌手,返回json格式
109
+ :param keyword: 歌手关键词
110
+ :return: 歌手信息json
111
+ """
112
+ url = f"https://ncm.nekogan.com/ugc/artist/search?keyword={keyword}"
113
+ try:
114
+ cookie = _get_cookie()
115
+ resp = requests.get(url, timeout=5, cookies=cookie)
116
+ resp.raise_for_status()
117
+ data = resp.json()
118
+ data_field = data.get("data")
119
+ if not data_field or "list" not in data_field:
120
+ return {"code": 404, "msg": "未找到相关歌手", "data": {}}
121
+ artists = data_field.get("list", [])
122
+ if not artists:
123
+ return {"code": 404, "msg": "未找到相关歌手", "data": {}}
124
+ artist = artists[0]
125
+ return {
126
+ "code": 200,
127
+ "msg": "success",
128
+ "data": {
129
+ "artistName": artist.get("artistName", "未知"),
130
+ "artistId": artist.get("artistId", "未知"),
131
+ "avatar": artist.get("artistAvatarPicUrl", "")
132
+ }
133
+ }
134
+ except Exception as e:
135
+ return {"code": 500, "msg": f"请求失败: {e}", "data": {}}
136
+
137
+ def _get_artist_id(keyword: str) -> dict:
138
+ """
139
+ 根据关键词搜索歌手,返回第一个歌手ID,json格式
140
+ :param keyword: 歌手关键词
141
+ :return: 歌手ID json
142
+ """
143
+ result = _search_artist(keyword)
144
+ if result.get("code") == 200 and "artistId" in result.get("data", {}):
145
+ return {"code": 200, "msg": "success", "id": str(result["data"]["artistId"])}
146
+ return {"code": 404, "msg": "未找到相关歌手ID", "id": ""}
147
+
148
+
149
+ def _get_artist_info(keyword: str) -> dict:
150
+ """
151
+ 根据关键词获取歌手详细信息,先查id再查详情,返回json
152
+ :param keyword: 歌手关键词
153
+ :return: 歌手详细信息json
154
+ """
155
+ id_json = _get_artist_id(keyword)
156
+ artist_id = id_json.get("id", "")
157
+ if not artist_id or not artist_id.isdigit():
158
+ return {"code": 404, "msg": f"未找到歌手ID,原因:{id_json}", "data": {}}
159
+ url = f"https://ncm.nekogan.com/ugc/artist/get?id={artist_id}"
160
+ try:
161
+ cookie = _get_cookie()
162
+ resp = requests.get(url, timeout=5, cookies=cookie)
163
+ resp.raise_for_status()
164
+ data = resp.json()
165
+ return {"code": 200, "msg": "success", "data": data}
166
+ except Exception as e:
167
+ return {"code": 500, "msg": f"请求失败: {e}", "data": {}}
168
+
169
+ def _get_artist_hot_songs(keyword: str) -> dict:
170
+ """
171
+ 根据关键词获取歌手最火的50首歌曲,先查id再查热门歌曲,返回json
172
+ :param keyword: 歌手关键词
173
+ :return: 热门歌曲json
174
+ """
175
+ id_json = _get_artist_id(keyword)
176
+ artist_id = id_json.get("id", "")
177
+ if not artist_id or not artist_id.isdigit():
178
+ return {"code": 404, "msg": f"未找到歌手ID,原因:{id_json}", "data": []}
179
+ url = f"https://ncm.nekogan.com/artist/top/song?id={artist_id}"
180
+ try:
181
+ cookie = _get_cookie()
182
+ resp = requests.get(url, timeout=5, cookies=cookie)
183
+ resp.raise_for_status()
184
+ data = resp.json()
185
+ return {"code": 200, "msg": "success", "data": data}
186
+ except Exception as e:
187
+ return {"code": 500, "msg": f"请求失败: {e}", "data": []}
188
+
189
+
190
+ def _get_music_url(keywords: str, level: str = "standard") -> dict:
191
+ """
192
+ 根据关键词获取音乐播放url,先查歌曲id再查url,返回json
193
+ :param keywords: 歌曲关键词
194
+ :param level: 音质等级
195
+ :return: 音乐url json
196
+ """
197
+ id_json = _get_song_id(keywords)
198
+ song_id = id_json.get("id", "")
199
+ if not song_id or not song_id.isdigit():
200
+ return {"code": 404, "msg": f"未找到歌曲ID,原因:{id_json}", "data": {}}
201
+ url = f"https://ncm.nekogan.com/song/url/v1?id={song_id}&level={level}"
202
+ try:
203
+ resp = requests.get(url, timeout=5)
204
+ resp.raise_for_status()
205
+ data = resp.json()
206
+ return {"code": 200, "msg": "success", "data": data}
207
+ except Exception as e:
208
+ return {"code": 500, "msg": f"请求失败: {e}", "data": {}}
209
+
210
+
211
+ def _get_song_comment(keywords: str) -> dict:
212
+ """
213
+ 根据关键词获取歌曲评论,只保留有用信息,返回json
214
+ :param keywords: 歌曲关键词
215
+ :return: 歌曲评论json
216
+ """
217
+ id_json = _get_song_id(keywords)
218
+ song_id = id_json.get("id", "")
219
+ if not song_id or not song_id.isdigit():
220
+ return {"code": 404, "msg": f"未找到歌曲ID,原因:{id_json}", "data": []}
221
+ url = f"https://ncm.nekogan.com/comment/music?id={song_id}"
222
+ try:
223
+ resp = requests.get(url, timeout=5)
224
+ resp.raise_for_status()
225
+ data = resp.json()
226
+ hot_comments = data.get("hotComments", [])
227
+ # 只保留有用字段
228
+ result = []
229
+ for c in hot_comments:
230
+ user = c.get("user", {})
231
+ result.append({
232
+ "nickname": user.get("nickname", "未知"),
233
+ "content": c.get("content", ""),
234
+ "likedCount": c.get("likedCount", 0),
235
+ "timeStr": c.get("timeStr", "")
236
+ })
237
+ return {"code": 200, "msg": "success", "data": result}
238
+ except Exception as e:
239
+ return {"code": 500, "msg": f"请求失败: {e}", "data": []}
240
+
241
+
242
+ def _get_playlist(id):
243
+ """
244
+ 获取歌单详情,只返回关键字段。
245
+ 参数: id (歌单id)
246
+ 返回: JSON
247
+ """
248
+ url = f"https://ncm.nekogan.com/playlist/detail?id={id}"
249
+ try:
250
+ resp = requests.get(url, timeout=10)
251
+ data = resp.json()
252
+ if data.get("code") != 200 or "playlist" not in data:
253
+ return {"code": 500, "msg": "请求失败"}
254
+ pl = data["playlist"]
255
+ result = {
256
+ "id": pl.get("id"),
257
+ "name": pl.get("name"),
258
+ "coverImgUrl": pl.get("coverImgUrl"),
259
+ "userId": pl.get("userId"),
260
+ "createTime": pl.get("createTime"),
261
+ "trackCount": pl.get("trackCount"),
262
+ "playCount": pl.get("playCount"),
263
+ "subscribedCount": pl.get("subscribedCount"),
264
+ "description": pl.get("description"),
265
+ "tags": pl.get("tags", []),
266
+ "tracks": []
267
+ }
268
+ for t in pl.get("tracks", []):
269
+ track = {
270
+ "id": t.get("id"),
271
+ "name": t.get("name"),
272
+ "ar": [a.get("name") for a in t.get("ar", [])],
273
+ "al": t.get("al", {}).get("name"),
274
+ "alia": t.get("alia", [])
275
+ }
276
+ result["tracks"].append(track)
277
+ return result
278
+ except Exception as e:
279
+ return {"code": 500, "msg": str(e)}
280
+
281
+
282
+ def _get_toplist(name: str) -> dict:
283
+ """
284
+ 根据排行榜名获取榜单详情(先查id再查_get_playlist)
285
+ :param name: 榜单名称
286
+ :return: 榜单详情json
287
+ """
288
+ try:
289
+ path = os.path.join(os.path.dirname(__file__), "toplist.json")
290
+ with open(path, "r", encoding="utf-8") as f:
291
+ data = json.load(f)
292
+ toplists = data.get("toplists", [])
293
+ match = next((item for item in toplists if item.get("name") == name), None)
294
+ if not match:
295
+ return {"code": 404, "msg": f"未找到榜单: {name}"}
296
+ return _get_playlist(match["id"])
297
+ except Exception as e:
298
+ return {"code": 500, "msg": f"读取榜单失败: {e}"}
299
+
300
+
301
+ def _get_similar_songs(keywords: str) -> dict:
302
+ """
303
+ 根据关键词获取相似音乐(先查id再查/simi/song)
304
+ :param keywords: 歌曲关键词
305
+ :return: 相似音乐json
306
+ """
307
+ id_json = _get_song_id(keywords)
308
+ song_id = id_json.get("id", "")
309
+ if not song_id or not song_id.isdigit():
310
+ return {"code": 404, "msg": f"未找到歌曲ID,原因:{id_json}", "data": []}
311
+ url = f"https://ncm.nekogan.com/simi/song?id={song_id}"
312
+ try:
313
+ resp = requests.get(url, timeout=5)
314
+ resp.raise_for_status()
315
+ data = resp.json()
316
+ songs = data.get("songs", [])
317
+ result = []
318
+ for s in songs:
319
+ result.append({
320
+ "id": s.get("id"),
321
+ "name": s.get("name"),
322
+ "artists": [a.get("name") for a in s.get("artists", [])],
323
+ "album": s.get("album", {}).get("name"),
324
+ "duration": s.get("duration")
325
+ })
326
+ return {"code": 200, "msg": "success", "data": result}
327
+ except Exception as e:
328
+ return {"code": 500, "msg": f"请求失败: {e}", "data": []}
329
+
330
+
331
+ def _get_style_songs(style_name: str) -> dict:
332
+ """
333
+ 根据曲风名获取对应曲风id,再查 /style/song?tagId=xxx,返回歌曲列表。
334
+ :param style_name: 曲风名
335
+ :return: 歌曲列表json
336
+ """
337
+
338
+ # 读取 styleList.json
339
+ try:
340
+ path = os.path.join(os.path.dirname(__file__), "styleList.json")
341
+ with open(path, "r", encoding="utf-8") as f:
342
+ style_list = json.load(f)
343
+ except Exception as e:
344
+ return {"code": 500, "msg": f"读取曲风列表失败: {e}", "data": {}}
345
+
346
+ tag_id = None
347
+ for item in style_list:
348
+ if item.get("tagName") == style_name:
349
+ tag_id = item.get("tagId")
350
+ break
351
+ if not tag_id:
352
+ return {"code": 404, "msg": f"未找到曲风: {style_name}", "data": {}}
353
+
354
+ url = f"https://ncm.nekogan.com/style/song?tagId={tag_id}"
355
+ try:
356
+ cookie = _get_cookie()
357
+ resp = requests.get(url, timeout=10, cookies=cookie)
358
+ resp.raise_for_status()
359
+ data = resp.json()
360
+ page = data.get("page", {})
361
+ songs = data.get("songs")
362
+ # 自动适配嵌套结构
363
+ if songs is None:
364
+ for v in data.values():
365
+ if isinstance(v, dict) and "songs" in v:
366
+ songs = v["songs"]
367
+ break
368
+ elif isinstance(v, list):
369
+ for item in v:
370
+ if isinstance(item, dict) and "songs" in item:
371
+ songs = item["songs"]
372
+ break
373
+ if songs is None:
374
+ songs = []
375
+ # 只保留关键信息
376
+ result = []
377
+ for s in songs:
378
+ if isinstance(s, dict):
379
+ result.append({
380
+ "id": s.get("id"),
381
+ "name": s.get("name"),
382
+ "artists": [a.get("name") for a in s.get("ar", [])],
383
+ "album": s.get("al", {}).get("name"),
384
+ "duration": s.get("dt")
385
+ })
386
+ return {"code": 200, "msg": "success", "data": result}
387
+ except Exception as e:
388
+ return {"code": 500, "msg": f"请求失败: {e}", "data": {}}
389
+
390
+
391
+
392
+ def _get_uid(nickname: str) -> dict:
393
+ """
394
+ 根据用户昵称获取其uid
395
+ :param nickname: 用户昵称
396
+ :return: 用户uid json
397
+ """
398
+ url = f"https://ncm.nekogan.com/get/userids?nicknames={nickname}"
399
+ try:
400
+ resp = requests.get(url, timeout=5)
401
+ resp.raise_for_status()
402
+ data = resp.json()
403
+ # 适配 nicknames 字段结构
404
+ nick_dict = data.get("nicknames", {})
405
+ if not nick_dict:
406
+ return {"code": 404, "msg": "未找到用户", "data": {}}
407
+ # 取第一个昵称和uid
408
+ for n, uid in nick_dict.items():
409
+ return {"code": 200, "msg": "success", "data": {"uid": uid, "nickname": n}}
410
+ return {"code": 404, "msg": "未找到用户", "data": {}}
411
+ except Exception as e:
412
+ return {"code": 500, "msg": f"请求失败: {e}", "data": {}}
413
+
414
+ def _get_user_playlist(nickname: str) -> dict:
415
+ """
416
+ 输入用户昵称,获取用户歌单(只保留必要字段)
417
+ :param nickname: 用户昵称
418
+ :return: 歌单列表json
419
+ """
420
+ # 第一步:获取uid
421
+ uid_result = _get_uid(nickname)
422
+ if uid_result.get("code") != 200 or "uid" not in uid_result.get("data", {}):
423
+ return {"code": 404, "msg": f"未找到用户: {nickname}", "data": []}
424
+ uid = uid_result["data"]["uid"]
425
+ # 第二步:获取歌单
426
+ url = f"https://ncm.nekogan.com/user/playlist?uid={uid}"
427
+ try:
428
+ resp = requests.get(url, timeout=5)
429
+ resp.raise_for_status()
430
+ data = resp.json()
431
+ playlists = data.get("playlist", [])
432
+ result = []
433
+ for pl in playlists:
434
+ result.append({
435
+ "id": pl.get("id"),
436
+ "name": pl.get("name"),
437
+ "coverImgUrl": pl.get("coverImgUrl"),
438
+ "trackCount": pl.get("trackCount"),
439
+ "playCount": pl.get("playCount"),
440
+ "creator": {
441
+ "userId": pl.get("creator", {}).get("userId"),
442
+ "nickname": pl.get("creator", {}).get("nickname"),
443
+ "avatarUrl": pl.get("creator", {}).get("avatarUrl")
444
+ }
445
+ })
446
+ return {"code": 200, "msg": "success", "data": result}
447
+ except Exception as e:
448
+ return {"code": 500, "msg": f"请求失败: {e}", "data": []}
449
+
450
+
451
+ def _return_stylelist() -> dict:
452
+ """
453
+ 返回所有风格名称(不含id),从styleList.json读取。
454
+ :return: {"code": 200, "msg": "success", "data": [风格名列表]}
455
+ """
456
+ try:
457
+ path = os.path.join(os.path.dirname(__file__), "styleList.json")
458
+ with open(path, "r", encoding="utf-8") as f:
459
+ style_list = json.load(f)
460
+ names = [item.get("tagName", "") for item in style_list if item.get("tagName")]
461
+ return {"code": 200, "msg": "success", "data": names}
462
+ except Exception as e:
463
+ return {"code": 500, "msg": f"读取风格列表失败: {e}", "data": []}
464
+
465
+
466
+ def _return_toplist_name() -> dict:
467
+ """
468
+ 返回所有排行榜名称(不含id),从toplist.json读取。
469
+ :return: {"code": 200, "msg": "success", "data": [榜单名列表]}
470
+ """
471
+ try:
472
+ path = os.path.join(os.path.dirname(__file__), "toplist.json")
473
+ with open(path, "r", encoding="utf-8") as f:
474
+ data = json.load(f)
475
+ toplists = data.get("toplists", [])
476
+ names = [item.get("name", "") for item in toplists if item.get("name")]
477
+ return {"code": 200, "msg": "success", "data": names}
478
+ except Exception as e:
479
+ return {"code": 500, "msg": f"读取榜单列表失败: {e}", "data": []}
480
+
481
+
482
+
483
+ def _login_anonymous() -> dict:
484
+ url = "https://ncm.nekogan.com/register/anonimous"
485
+ try:
486
+ resp = requests.get(url, timeout=10)
487
+ resp.raise_for_status()
488
+ data = resp.json()
489
+ cookie = resp.cookies.get_dict()
490
+ global global_cookie
491
+ global_cookie = cookie
492
+ _save_cookie_to_file(cookie)
493
+ return {"code": 200, "msg": "游客登录成功", "cookie": cookie, "data": data}
494
+ except Exception as e:
495
+ return {"code": 500, "msg": f"游客登录失败: {e}", "cookie": {}, "data": {}}
496
+
497
+ def _login_refresh() -> dict:
498
+ url = "https://ncm.nekogan.com/login/refresh"
499
+ try:
500
+ resp = requests.get(url, timeout=10)
501
+ resp.raise_for_status()
502
+ data = resp.json()
503
+ cookie = resp.cookies.get_dict()
504
+ return {"code": 200, "msg": "登录状态刷新成功", "cookie": cookie, "data": data}
505
+ except Exception as e:
506
+ return {"code": 500, "msg": f"刷新登录失败: {e}", "cookie": {}, "data": {}}
507
+
508
+
509
+ def _start_netease_music(exe_path: str) -> dict:
510
+ """
511
+ 启动本地网易云音乐客户端
512
+ :param exe_path: 网易云音乐客户端的 exe 路径
513
+ :return: 启动结果 json
514
+ """
515
+ try:
516
+ subprocess.Popen(exe_path)
517
+ return {"code": 200, "msg": "网易云音乐已启动", "exe_path": exe_path}
518
+ except Exception as e:
519
+ return {"code": 500, "msg": f"启动失败: {e}", "exe_path": exe_path}
@@ -0,0 +1,65 @@
1
+ {
2
+ "toplists": [
3
+ {"name": "飙升榜", "id": 19723756},
4
+ {"name": "新歌榜", "id": 3779629},
5
+ {"name": "原创榜", "id": 2884035},
6
+ {"name": "热歌榜", "id": 3778678},
7
+ {"name": "网易云中文说唱榜", "id": 991319590},
8
+ {"name": "网易云古典榜", "id": 71384707},
9
+ {"name": "网易云电音榜", "id": 1978921795},
10
+ {"name": "网易云全球说唱榜", "id": 14028249541},
11
+ {"name": "潮流风向榜", "id": 13372522766},
12
+ {"name": "音乐合伙人推荐榜", "id": 12911403728},
13
+ {"name": "音乐合伙人热歌榜", "id": 12911589513},
14
+ {"name": "音乐合伙人留名榜", "id": 12911619970},
15
+ {"name": "音乐合伙人高分新歌榜", "id": 12911379734},
16
+ {"name": "音乐合伙人高分榜", "id": 12768855486},
17
+ {"name": "黑胶VIP爱听榜", "id": 5453912201},
18
+ {"name": "网易云ACG榜", "id": 71385702},
19
+ {"name": "网易云韩语榜", "id": 745956260},
20
+ {"name": "UK排行榜周榜", "id": 180106},
21
+ {"name": "美国Billboard榜", "id": 60198},
22
+ {"name": "Beatport全球电子舞曲榜", "id": 3812895},
23
+ {"name": "KTV唛榜", "id": 21845217},
24
+ {"name": "日本Oricon榜", "id": 60131},
25
+ {"name": "网易云欧美热歌榜", "id": 2809513713},
26
+ {"name": "网易云欧美新歌榜", "id": 2809577409},
27
+ {"name": "蛋仔派对听歌榜", "id": 8532443277},
28
+ {"name": "AI歌曲榜", "id": 9651277674},
29
+ {"name": "黑胶VIP新歌榜", "id": 7785123708},
30
+ {"name": "黑胶VIP热歌榜", "id": 7785066739},
31
+ {"name": "黑胶VIP爱搜榜", "id": 7785091694},
32
+ {"name": "实时热度榜", "id": 8246775932},
33
+ {"name": "法国 NRJ Vos Hits 周榜", "id": 27135204},
34
+ {"name": "网易云ACG动画榜", "id": 3001835560},
35
+ {"name": "网易云ACG游戏榜", "id": 3001795926},
36
+ {"name": "网易云ACG VOCALOID榜", "id": 3001890046},
37
+ {"name": "网易云日语榜", "id": 5059644681},
38
+ {"name": "网易云摇滚榜", "id": 5059633707},
39
+ {"name": "网易云国风榜", "id": 5059642708},
40
+ {"name": "潜力爆款榜", "id": 5338990334},
41
+ {"name": "网易云民谣榜", "id": 5059661515},
42
+ {"name": "听歌识曲榜", "id": 6688069460},
43
+ {"name": "网络热歌榜", "id": 6723173524},
44
+ {"name": "俄语榜", "id": 6732051320},
45
+ {"name": "越南语榜", "id": 6732014811},
46
+ {"name": "中文慢摇DJ榜", "id": 6886768100},
47
+ {"name": "俄罗斯top hit流行音乐榜", "id": 6939992364},
48
+ {"name": "泰语榜", "id": 7095271308},
49
+ {"name": "BEAT排行榜", "id": 7356827205},
50
+ {"name": "星云榜VOL.30 Wolf Alice新专来袭,人生应该拥有不一样的颜色", "id": 7325478166},
51
+ {"name": "LOOK直播歌曲榜", "id": 7603212484},
52
+ {"name": "赏音榜", "id": 7775163417},
53
+ {"name": "黑胶VIP限免榜", "id": 12344472377},
54
+ {"name": "吉利车友爱听榜", "id": 12717025277},
55
+ {"name": "昊铂车友爱听榜", "id": 10131772880},
56
+ {"name": "埃安车友爱听榜", "id": 10162841534},
57
+ {"name": "欧美R&B榜", "id": 12225155968},
58
+ {"name": "理想车友爱听榜", "id": 8703052295},
59
+ {"name": "比亚迪车友爱听榜", "id": 8702582160},
60
+ {"name": "蔚来车友爱听榜", "id": 8703220480},
61
+ {"name": "极氪车友爱听榜", "id": 8702982391},
62
+ {"name": "特斯拉车友爱听榜", "id": 8703179781},
63
+ {"name": "乐夏榜", "id": 8661209031}
64
+ ]
65
+ }
@@ -0,0 +1,69 @@
1
+ Metadata-Version: 2.4
2
+ Name: ben-music-mcp
3
+ Version: 0.1.5
4
+ Summary:
5
+ Author: ben
6
+ Author-email: 2014911413@qq.com
7
+ Requires-Python: >=3.12
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Programming Language :: Python :: 3.14
12
+ Requires-Dist: fastmcp (>=2.13.0.2)
13
+ Requires-Dist: requests (>=2.32.4)
14
+ Description-Content-Type: text/markdown
15
+
16
+ # ben-music-mcp
17
+
18
+ 本产品是一套面向开发者和智能体的音乐信息服务工具,专注于高效、专业的音乐数据获取与个性化推荐。所有接口均采用标准 JSON 格式,便于自动化集成与二次开发。
19
+
20
+ ---
21
+
22
+ ## 产品功能概览
23
+
24
+ ### 歌曲与歌手信息
25
+ - **歌曲检索**:支持通过关键词精准获取歌曲详情,包括名称、艺人、专辑等核心信息。
26
+ - **歌词查询**:一键获取任意歌曲的完整歌词文本。
27
+ - **歌手检索与详情**:快速定位歌手并获取其详细资料及代表作品。
28
+ - **热门歌曲榜单**:自动获取歌手最受欢迎的曲目列表。
29
+
30
+ ### 音乐播放与评论
31
+ - **在线试听链接**:根据需求获取不同音质等级的音乐播放地址。
32
+ - **热门评论提取**:智能筛选并返回歌曲的高质量评论,便于用户快速了解口碑。
33
+ - **相似歌曲推荐**:基于音乐特征,智能推荐与目标歌曲风格相近的作品。
34
+
35
+ ### 歌单与榜单服务
36
+ - **歌单详情查询**:支持按歌单编号获取完整歌单信息,涵盖曲目、封面、创建者等。
37
+ - **排行榜检索**:覆盖主流及特色音乐榜单,助力发现最新流行趋势。
38
+ - **用户歌单获取**:根据用户昵称,自动获取其公开歌单列表。
39
+
40
+ ### 曲风与标签体系
41
+ - **曲风分类查询**:支持按风格名称获取对应曲风下的热门歌曲。
42
+ - **风格列表获取**:一键获取所有可选音乐风格,便于个性化筛选。
43
+ - **榜单名称获取**:快速获取所有可用排行榜名称,提升榜单检索效率。
44
+
45
+ ### 用户与客户端服务
46
+ - **用户身份查询**:通过昵称获取用户唯一标识,便于个性化服务。
47
+ - **本地客户端启动**:支持远程启动本地网易云音乐客户端,提升桌面体验。
48
+
49
+ ---
50
+
51
+
52
+ ## 接口返回格式
53
+ 所有接口均返回标准结构,便于前后端集成:
54
+
55
+ ```json
56
+ {
57
+ "code": 200, // 状态码,200 表示成功
58
+ "msg": "success", // 状态信息
59
+ "data": {...} // 具体数据内容
60
+ }
61
+ ```
62
+
63
+ ---
64
+
65
+ ## 技术与环境要求
66
+ - Python 3.8 及以上
67
+ - requests
68
+ - fastmcp
69
+
@@ -0,0 +1,10 @@
1
+ ben_music_mcp/__init__.py,sha256=l9rBZdXJcxQnhqDUar0rwjVOIiXOZHhQfH778VYH2LE,5309
2
+ ben_music_mcp/cookie.txt,sha256=lT4cD7TKM02Ci8rtJFM1zcd_9RSzjv_4e26sfFY7ihk,1346
3
+ ben_music_mcp/server.py,sha256=80KVPLOFBC7faAxf-l_iGbmiXZKoomM__5eKAcnjJ_c,79
4
+ ben_music_mcp/styleList.json,sha256=dtODcFS6MFLvSOfP4x1VoAf5TYoE-nOwbhczGwus1X0,1079
5
+ ben_music_mcp/tool.py,sha256=uRDWIisy6Hct9--NpAPxcIOqs5BcOFeeo8R04lqMZJg,19834
6
+ ben_music_mcp/toplist.json,sha256=HHNN_6LabK-lU5yoHq4RfJ1uDMOGgiKbCMksYVjNRQI,3423
7
+ ben_music_mcp-0.1.5.dist-info/entry_points.txt,sha256=Ii9fJl63ac3OgZb1AJkYvDST3hVZEA0nAujlhiqgpSA,52
8
+ ben_music_mcp-0.1.5.dist-info/METADATA,sha256=tPXF50w1jDAL_Nrm8cfcBD7ZQhfdFI0jUrzuwy32gU4,2586
9
+ ben_music_mcp-0.1.5.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
10
+ ben_music_mcp-0.1.5.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.2.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ ben-music-mcp=ben_music_mcp:main
3
+