mcp-ai-music 1.0.4 → 1.0.7

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.
Files changed (3) hide show
  1. package/README.md +260 -260
  2. package/dist/index.js +220 -58
  3. package/package.json +40 -40
package/README.md CHANGED
@@ -1,260 +1,260 @@
1
- # AI作曲 MCP 服务
2
-
3
- **版本 (Version):** 1.0.0
4
-
5
- ## 描述 (Description)
6
-
7
- `mcp-ai-music` 是一个基于 Suno4.5 API 的 AI 作曲 MCP 服务,提供音乐生成、翻唱、扩展和进度查询功能。该服务支持多种音乐风格,可以生成原创音乐、对现有音乐进行翻唱转换,以及扩展音乐长度。
8
-
9
- ## 功能特点
10
-
11
- - **原创音乐生成**:根据提示词和风格生成全新的音乐作品
12
- - **音乐翻唱**:将现有音乐转换为不同风格,保留核心旋律
13
- - **音乐扩展**:在保持原始风格的基础上延长音乐时长
14
- - **实时进度查询**:支持轮询查询任务进度,实时了解生成状态
15
- - **多模型支持**:支持 V3_5、V4、V4_5 多个AI模型版本
16
- - **纯音乐模式**:支持生成无歌词的纯音乐版本
17
-
18
- ## 环境配置
19
-
20
- > **重要**:请配置以下必填环境变量:
21
- > - `SUNO_API_KEY`:Suno API 密钥
22
- > - `SUNO_SERVICE_BASE`:服务网关地址,默认 `https://www.mcpcn.cc`
23
-
24
- ### 费用说明
25
- - 生成音乐:¥0.438/次
26
- - 翻唱音乐:¥0.438/次
27
- - 扩展音乐:¥0.438/次
28
- - 查询进度:免费
29
-
30
- ## 使用方法
31
-
32
- ```json
33
- {
34
- "mcpServers": {
35
- "mcp-ai-music": {
36
- "command": "node",
37
- "args": [
38
- "dist/index.js"
39
- ],
40
- "env": {
41
- "SUNO_API_KEY": "您的Suno API密钥",
42
- "SUNO_SERVICE_BASE": "https://www.mcpcn.cc/api"
43
- },
44
- "autoApprove": [
45
- "generate_music",
46
- "cover_music",
47
- "extend_music",
48
- "query_progress"
49
- ]
50
- }
51
- }
52
- }
53
- ```
54
-
55
- ## 可用工具 (Available Tools)
56
-
57
- 该服务提供以下4个工具:
58
-
59
- ### 1. `generate_music` - 生成音乐
60
-
61
- 生成原创音乐作品。
62
-
63
- **输入参数:**
64
- - `prompt` (必需): 音乐描述提示词,详细描述想要的音乐风格、情感、乐器等
65
- - V3_5/V4 模型:最多 3000 字符
66
- - V4_5 模型:最多 5000 字符
67
- - `style` (可选): 音乐风格,如"古典"、"流行"、"摇滚"等
68
- - V3_5/V4 模型:最多 200 字符
69
- - V4_5 模型:最多 1000 字符
70
- - `title` (可选): 音乐标题,最多 80 字符
71
- - `instrumental` (可选): 是否生成纯音乐(无歌词),默认 false
72
- - `model` (可选): AI模型版本,可选 "V3_5"、"V4"、"V4_5",默认 "V4_5"
73
- - `negativeTags` (可选): 负面标签,描述不想要的音乐元素
74
-
75
- **调用示例:**
76
- ```json
77
- {
78
- "name": "generate_music",
79
- "arguments": {
80
- "prompt": "一段平静舒缓的钢琴曲,带有柔和的旋律,适合冥想和放松",
81
- "style": "古典",
82
- "title": "宁静钢琴冥想",
83
- "instrumental": true,
84
- "model": "V4_5",
85
- "negativeTags": "重金属, 强节奏鼓点"
86
- }
87
- }
88
- ```
89
-
90
- ### 2. `cover_music` - 翻唱音乐
91
-
92
- 将现有音乐转换为新的风格,保留核心旋律。
93
-
94
- **输入参数:**
95
- - `uploadUrl` (必需): 要翻唱的音频文件URL,音频长度不超过2分钟
96
- - `prompt` (必需): 翻唱风格描述
97
- - `style` (可选): 目标音乐风格
98
- - `title` (可选): 翻唱版本标题
99
- - `instrumental` (可选): 是否生成纯音乐版本,默认 false
100
- - `model` (可选): AI模型版本,默认 "V4_5"
101
- - `negativeTags` (可选): 负面标签
102
-
103
- **调用示例:**
104
- ```json
105
- {
106
- "name": "cover_music",
107
- "arguments": {
108
- "uploadUrl": "https://example.com/audio.mp3",
109
- "prompt": "将这首歌转换为爵士风格",
110
- "style": "爵士",
111
- "title": "爵士翻唱版",
112
- "instrumental": false
113
- }
114
- }
115
- ```
116
-
117
- ### 3. `extend_music` - 扩展音乐
118
-
119
- 在保留原始音频风格的同时扩展音轨长度。
120
-
121
- **输入参数:**
122
- - `uploadUrl` (必需): 要扩展的音频文件URL,音频长度不超过2分钟
123
- - `prompt` (必需): 扩展描述,如"用更多舒缓的音符延长音乐"
124
- - `continueAt` (必需): 从音频的第几秒开始扩展,必须大于0且小于音频总时长
125
- - `style` (可选): 保持的音乐风格
126
- - `title` (可选): 扩展版本标题
127
- - `instrumental` (可选): 是否生成纯音乐版本,默认 false
128
- - `model` (可选): AI模型版本,必须与源音乐保持一致,默认 "V4_5"
129
- - `negativeTags` (可选): 负面标签
130
-
131
- **调用示例:**
132
- ```json
133
- {
134
- "name": "extend_music",
135
- "arguments": {
136
- "uploadUrl": "https://example.com/audio.mp3",
137
- "prompt": "用更多舒缓的音符延长音乐,保持原有的宁静氛围",
138
- "continueAt": 60,
139
- "style": "古典",
140
- "title": "宁静钢琴延长版",
141
- "instrumental": true
142
- }
143
- }
144
- ```
145
-
146
- ### 4. `query_progress` - 查询进度
147
-
148
- 查询音乐生成任务的进度状态。
149
-
150
- > **重要提示**:由于AI生成音乐需要时间,大模型需要轮询此接口来获取任务状态和结果。建议每10-30秒查询一次,直到状态为'complete'或'failed'。
151
-
152
- **输入参数:**
153
- - `taskId` (必需): 音乐生成任务的ID(从其他工具的返回结果中获取)
154
-
155
- **调用示例:**
156
- ```json
157
- {
158
- "name": "query_progress",
159
- "arguments": {
160
- "taskId": "task_12345"
161
- }
162
- }
163
- ```
164
-
165
- **状态说明:**
166
- - `processing`: 正在处理中
167
- - `text`: 文本生成完成,正在生成音频
168
- - `first`: 第一首音乐生成完成
169
- - `complete`: 任务完成
170
- - `failed`: 任务失败
171
-
172
- ## 工作流程示例
173
-
174
- ### 生成原创音乐的完整流程:
175
-
176
- 1. **发起生成请求**
177
- ```json
178
- {
179
- "name": "generate_music",
180
- "arguments": {
181
- "prompt": "一首欢快的流行歌曲,适合夏天",
182
- "style": "流行",
183
- "title": "夏日阳光"
184
- }
185
- }
186
- ```
187
-
188
- 2. **获取任务ID**
189
- ```json
190
- {
191
- "taskId": "task_abc123",
192
- "status": "pending",
193
- "message": "音乐生成任务已提交,请使用query_progress工具查询进度。",
194
- "cost": "¥0.438"
195
- }
196
- ```
197
-
198
- 3. **轮询查询进度**
199
- ```json
200
- {
201
- "name": "query_progress",
202
- "arguments": {
203
- "taskId": "task_abc123"
204
- }
205
- }
206
- ```
207
-
208
- 4. **获取最终结果**
209
- 当状态变为'complete'时,会返回包含音乐文件信息的完整结果。
210
-
211
- ## 注意事项
212
-
213
- > 也需要设置 `SUNO_SERVICE_BASE`(默认 `https://www.mcpcn.cc`),否则将无法正常访问服务。
214
-
215
- 1. **API密钥安全**:请确保 `SUNO_API_KEY` 环境变量已正确设置,不要在代码中硬编码API密钥。
216
-
217
- 2. **文件大小限制**:上传的音频文件长度不得超过2分钟。
218
-
219
- 3. **文件保存期限**:生成的音乐文件在服务器上保留15天后会被删除。
220
-
221
- 4. **费用控制**:每次调用生成、翻唱、扩展功能都会产生¥0.438的费用,请合理使用。
222
-
223
- 5. **轮询间隔**:建议查询进度的间隔为10-30秒,避免过于频繁的请求。
224
-
225
- 6. **模型兼容性**:在扩展音乐时,使用的模型版本必须与源音乐的生成模型保持一致。
226
-
227
- 7. **字符限制**:请注意各个参数的字符长度限制,超出限制会导致请求失败。
228
-
229
- ## 错误处理
230
-
231
- 服务会返回详细的错误信息,包括:
232
- - 参数验证错误
233
- - API请求失败
234
- - 字符长度超限
235
- - 任务状态异常
236
-
237
- 所有错误都会在响应中通过 `isError` 字段标识,并在 `errorMessage` 字段中提供具体的错误描述。
238
-
239
- ## 技术实现
240
-
241
- - 基于 Model Context Protocol (MCP) SDK 构建
242
- - 使用 Suno4.5 API 进行音乐生成
243
- - 支持 TypeScript 开发
244
- - 提供完整的类型定义和输入验证
245
-
246
- ## 开发和构建
247
-
248
- ```bash
249
- # 安装依赖
250
- npm install
251
-
252
- # 开发模式运行
253
- npm run dev
254
-
255
- # 构建项目
256
- npm run build
257
-
258
- # 启动服务
259
- npm start
260
- ```
1
+ # AI作曲 MCP 服务
2
+
3
+ **版本 (Version):** 1.0.0
4
+
5
+ ## 描述 (Description)
6
+
7
+ `mcp-ai-music` 是一个基于 Suno4.5 API 的 AI 作曲 MCP 服务,提供音乐生成、翻唱、扩展和进度查询功能。该服务支持多种音乐风格,可以生成原创音乐、对现有音乐进行翻唱转换,以及扩展音乐长度。
8
+
9
+ ## 功能特点
10
+
11
+ - **原创音乐生成**:根据提示词和风格生成全新的音乐作品
12
+ - **音乐翻唱**:将现有音乐转换为不同风格,保留核心旋律
13
+ - **音乐扩展**:在保持原始风格的基础上延长音乐时长
14
+ - **实时进度查询**:支持轮询查询任务进度,实时了解生成状态
15
+ - **多模型支持**:支持 V3_5、V4、V4_5 多个AI模型版本
16
+ - **纯音乐模式**:支持生成无歌词的纯音乐版本
17
+
18
+ ## 环境配置
19
+
20
+ > **重要**:请配置以下必填环境变量:
21
+ > - `SUNO_API_KEY`:Suno API 密钥
22
+ > - `SUNO_SERVICE_BASE`:服务网关地址,默认 `https://www.mcpcn.cc`
23
+
24
+ ### 费用说明
25
+ - 生成音乐:¥0.438/次
26
+ - 翻唱音乐:¥0.438/次
27
+ - 扩展音乐:¥0.438/次
28
+ - 查询进度:免费
29
+
30
+ ## 使用方法
31
+
32
+ ```json
33
+ {
34
+ "mcpServers": {
35
+ "mcp-ai-music": {
36
+ "command": "node",
37
+ "args": [
38
+ "dist/index.js"
39
+ ],
40
+ "env": {
41
+ "SUNO_API_KEY": "您的Suno API密钥",
42
+ "SUNO_SERVICE_BASE": "https://www.mcpcn.cc/api"
43
+ },
44
+ "autoApprove": [
45
+ "generate_music",
46
+ "cover_music",
47
+ "extend_music",
48
+ "query_progress"
49
+ ]
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ ## 可用工具 (Available Tools)
56
+
57
+ 该服务提供以下4个工具:
58
+
59
+ ### 1. `generate_music` - 生成音乐
60
+
61
+ 生成原创音乐作品。
62
+
63
+ **输入参数:**
64
+ - `prompt` (必需): 音乐描述提示词,详细描述想要的音乐风格、情感、乐器等
65
+ - V3_5/V4 模型:最多 3000 字符
66
+ - V4_5 模型:最多 5000 字符
67
+ - `style` (可选): 音乐风格,如"古典"、"流行"、"摇滚"等
68
+ - V3_5/V4 模型:最多 200 字符
69
+ - V4_5 模型:最多 1000 字符
70
+ - `title` (可选): 音乐标题,最多 80 字符
71
+ - `instrumental` (可选): 是否生成纯音乐(无歌词),默认 false
72
+ - `model` (可选): AI模型版本,可选 "V3_5"、"V4"、"V4_5",默认 "V4_5"
73
+ - `negativeTags` (可选): 负面标签,描述不想要的音乐元素
74
+
75
+ **调用示例:**
76
+ ```json
77
+ {
78
+ "name": "generate_music",
79
+ "arguments": {
80
+ "prompt": "一段平静舒缓的钢琴曲,带有柔和的旋律,适合冥想和放松",
81
+ "style": "古典",
82
+ "title": "宁静钢琴冥想",
83
+ "instrumental": true,
84
+ "model": "V4_5",
85
+ "negativeTags": "重金属, 强节奏鼓点"
86
+ }
87
+ }
88
+ ```
89
+
90
+ ### 2. `cover_music` - 翻唱音乐
91
+
92
+ 将现有音乐转换为新的风格,保留核心旋律。
93
+
94
+ **输入参数:**
95
+ - `uploadUrl` (必需): 要翻唱的音频文件URL,音频长度不超过2分钟
96
+ - `prompt` (必需): 翻唱风格描述
97
+ - `style` (可选): 目标音乐风格
98
+ - `title` (可选): 翻唱版本标题
99
+ - `instrumental` (可选): 是否生成纯音乐版本,默认 false
100
+ - `model` (可选): AI模型版本,默认 "V4_5"
101
+ - `negativeTags` (可选): 负面标签
102
+
103
+ **调用示例:**
104
+ ```json
105
+ {
106
+ "name": "cover_music",
107
+ "arguments": {
108
+ "uploadUrl": "https://example.com/audio.mp3",
109
+ "prompt": "将这首歌转换为爵士风格",
110
+ "style": "爵士",
111
+ "title": "爵士翻唱版",
112
+ "instrumental": false
113
+ }
114
+ }
115
+ ```
116
+
117
+ ### 3. `extend_music` - 扩展音乐
118
+
119
+ 在保留原始音频风格的同时扩展音轨长度。
120
+
121
+ **输入参数:**
122
+ - `uploadUrl` (必需): 要扩展的音频文件URL,音频长度不超过2分钟
123
+ - `prompt` (必需): 扩展描述,如"用更多舒缓的音符延长音乐"
124
+ - `continueAt` (必需): 从音频的第几秒开始扩展,必须大于0且小于音频总时长
125
+ - `style` (可选): 保持的音乐风格
126
+ - `title` (可选): 扩展版本标题
127
+ - `instrumental` (可选): 是否生成纯音乐版本,默认 false
128
+ - `model` (可选): AI模型版本,必须与源音乐保持一致,默认 "V4_5"
129
+ - `negativeTags` (可选): 负面标签
130
+
131
+ **调用示例:**
132
+ ```json
133
+ {
134
+ "name": "extend_music",
135
+ "arguments": {
136
+ "uploadUrl": "https://example.com/audio.mp3",
137
+ "prompt": "用更多舒缓的音符延长音乐,保持原有的宁静氛围",
138
+ "continueAt": 60,
139
+ "style": "古典",
140
+ "title": "宁静钢琴延长版",
141
+ "instrumental": true
142
+ }
143
+ }
144
+ ```
145
+
146
+ ### 4. `query_progress` - 查询进度
147
+
148
+ 查询音乐生成任务的进度状态。
149
+
150
+ > **重要提示**:由于AI生成音乐需要时间,大模型需要轮询此接口来获取任务状态和结果。建议每10-30秒查询一次,直到状态为'complete'或'failed'。
151
+
152
+ **输入参数:**
153
+ - `taskId` (必需): 音乐生成任务的ID(从其他工具的返回结果中获取)
154
+
155
+ **调用示例:**
156
+ ```json
157
+ {
158
+ "name": "query_progress",
159
+ "arguments": {
160
+ "taskId": "task_12345"
161
+ }
162
+ }
163
+ ```
164
+
165
+ **状态说明:**
166
+ - `processing`: 正在处理中
167
+ - `text`: 文本生成完成,正在生成音频
168
+ - `first`: 第一首音乐生成完成
169
+ - `complete`: 任务完成
170
+ - `failed`: 任务失败
171
+
172
+ ## 工作流程示例
173
+
174
+ ### 生成原创音乐的完整流程:
175
+
176
+ 1. **发起生成请求**
177
+ ```json
178
+ {
179
+ "name": "generate_music",
180
+ "arguments": {
181
+ "prompt": "一首欢快的流行歌曲,适合夏天",
182
+ "style": "流行",
183
+ "title": "夏日阳光"
184
+ }
185
+ }
186
+ ```
187
+
188
+ 2. **获取任务ID**
189
+ ```json
190
+ {
191
+ "taskId": "task_abc123",
192
+ "status": "pending",
193
+ "message": "音乐生成任务已提交,请使用query_progress工具查询进度。",
194
+ "cost": "¥0.438"
195
+ }
196
+ ```
197
+
198
+ 3. **轮询查询进度**
199
+ ```json
200
+ {
201
+ "name": "query_progress",
202
+ "arguments": {
203
+ "taskId": "task_abc123"
204
+ }
205
+ }
206
+ ```
207
+
208
+ 4. **获取最终结果**
209
+ 当状态变为'complete'时,会返回包含音乐文件信息的完整结果。
210
+
211
+ ## 注意事项
212
+
213
+ > 也需要设置 `SUNO_SERVICE_BASE`(默认 `https://www.mcpcn.cc`),否则将无法正常访问服务。
214
+
215
+ 1. **API密钥安全**:请确保 `SUNO_API_KEY` 环境变量已正确设置,不要在代码中硬编码API密钥。
216
+
217
+ 2. **文件大小限制**:上传的音频文件长度不得超过2分钟。
218
+
219
+ 3. **文件保存期限**:生成的音乐文件在服务器上保留15天后会被删除。
220
+
221
+ 4. **费用控制**:每次调用生成、翻唱、扩展功能都会产生¥0.438的费用,请合理使用。
222
+
223
+ 5. **轮询间隔**:建议查询进度的间隔为10-30秒,避免过于频繁的请求。
224
+
225
+ 6. **模型兼容性**:在扩展音乐时,使用的模型版本必须与源音乐的生成模型保持一致。
226
+
227
+ 7. **字符限制**:请注意各个参数的字符长度限制,超出限制会导致请求失败。
228
+
229
+ ## 错误处理
230
+
231
+ 服务会返回详细的错误信息,包括:
232
+ - 参数验证错误
233
+ - API请求失败
234
+ - 字符长度超限
235
+ - 任务状态异常
236
+
237
+ 所有错误都会在响应中通过 `isError` 字段标识,并在 `errorMessage` 字段中提供具体的错误描述。
238
+
239
+ ## 技术实现
240
+
241
+ - 基于 Model Context Protocol (MCP) SDK 构建
242
+ - 使用 Suno4.5 API 进行音乐生成
243
+ - 支持 TypeScript 开发
244
+ - 提供完整的类型定义和输入验证
245
+
246
+ ## 开发和构建
247
+
248
+ ```bash
249
+ # 安装依赖
250
+ npm install
251
+
252
+ # 开发模式运行
253
+ npm run dev
254
+
255
+ # 构建项目
256
+ npm run build
257
+
258
+ # 启动服务
259
+ npm start
260
+ ```
package/dist/index.js CHANGED
@@ -26,8 +26,8 @@ function getServiceBase() {
26
26
  process.env.SUNO_QUERY_BASE_URL ||
27
27
  process.env.SUNO_CALLBACK_URL);
28
28
  }
29
- // 构造回调地址
30
- function buildCallbackUrl(kind) {
29
+ // 构造回调地址,可附带 chatSessionId
30
+ function buildCallbackUrl(kind, chatSessionId) {
31
31
  const base = getServiceBase();
32
32
  if (!base) {
33
33
  throw new Error("未配置 SUNO_SERVICE_BASE(或兼容变量),用于设置服务端基地址");
@@ -37,7 +37,11 @@ function buildCallbackUrl(kind) {
37
37
  "upload-cover": "/suno/callback/upload-cover",
38
38
  "upload-extend": "/suno/callback/upload-extend",
39
39
  };
40
- return `${base.replace(/\/$/, "")}${pathMap[kind]}`;
40
+ const url = `${base.replace(/\/$/, "")}${pathMap[kind]}`;
41
+ if (chatSessionId) {
42
+ return `${url}?chat_session_id=${encodeURIComponent(chatSessionId)}`;
43
+ }
44
+ return url;
41
45
  }
42
46
  // 查询同样使用统一基地址
43
47
  function getQueryBase() {
@@ -85,17 +89,22 @@ const PROGRESS_QUERY_OUTPUT_SCHEMA = {
85
89
  // 工具定义
86
90
  const GENERATE_MUSIC_TOOL = {
87
91
  name: "generate_music",
88
- description: "生成音乐。根据提示词和风格生成原创音乐,支持自定义模式和纯音乐模式。\n\n注意:\n- 生成需要时间,请每10-30秒轮询调用 query_progress 查询进度工具,直到状态为 'complete' 或 'failed'。\n- 平台仅保存音频与封面文件 15 天,请及时下载保存。\n\n参数行为说明:\n- customMode: 是否启用自定义模式(默认 true)。\n- instrumental: 是否为纯音乐。\n- 当 customMode=true 且 instrumental=false 时,prompt 将作为精确歌词使用;\n 当 customMode=false 且 instrumental=false 时,将自动生成歌词;\n 当 instrumental=true 时,始终为纯音乐(不含歌词)。\n\n返回字段说明:\n- taskId: 任务ID,用于查询进度\n- status: 任务状态 (pending)\n- message: 状态描述",
92
+ description: "生成音乐。根据提示词和风格生成原创音乐,支持自定义模式和纯音乐模式。\n\n注意:\n- 生成需要时间,请每10-30秒轮询调用 query_progress 查询进度工具,直到状态为 'complete' 或 'failed'。\n- 平台仅保存音频与封面文件 15 天,请及时下载保存。\n\n参数行为说明:\n- customMode: 是否启用自定义模式(默认 true)。\n- instrumental: 是否为纯音乐。\n- 当 customMode=true 且 instrumental=false 时,可以同时提供 prompt(音乐描述)和 lyrics(精确歌词);\n 当 customMode=false 且 instrumental=false 时,只需 prompt,将自动生成歌词;\n 当 instrumental=true 时,始终为纯音乐(不含歌词),只需提供 prompt。\n\n返回字段说明:\n- taskId: 任务ID,用于查询进度\n- status: 任务状态 (pending)\n- message: 状态描述",
89
93
  inputSchema: {
90
94
  type: "object",
91
95
  properties: {
92
96
  prompt: {
93
97
  type: "string",
94
- description: "音乐描述提示词,详细描述想要的音乐风格、情感、乐器等。长度限制:V3_5和V4模型3000字符,V4_5模型5000字符。注意:当 customMode=true 且 instrumental=false 时,prompt 将作为精确歌词使用。",
98
+ description: "音乐描述提示词,详细描述想要的音乐风格、情感、乐器等。长度限制:V3_5和V4模型3000字符,V4_5模型5000字符。当 instrumental=false 时,可与 lyrics 字段配合使用。",
99
+ },
100
+ lyrics: {
101
+ type: "string",
102
+ description: "精确歌词内容,用于 customMode=true 且 instrumental=false 时。当提供此字段时,AI将按照此歌词演唱。长度限制:V3_5和V4模型3000字符,V4_5模型5000字符。",
103
+ default: "",
95
104
  },
96
105
  style: {
97
106
  type: "string",
98
- description: "音乐风格,如'古典'、'流行'、'摇滚'等。长度限制:V3_5和V4模型200字符,V4_5模型1000字符。",
107
+ description: "音乐风格,如'古典'、'流行'、'摇滚'、'电子'等。长度限制:V3_5和V4模型200字符,V4_5模型1000字符。",
99
108
  default: "",
100
109
  },
101
110
  title: {
@@ -105,12 +114,12 @@ const GENERATE_MUSIC_TOOL = {
105
114
  },
106
115
  customMode: {
107
116
  type: "boolean",
108
- description: "是否启用自定义模式。为 true 时:如果 instrumental=true,仅需提供 style 和 title;如果 instrumental=false,需要提供 style、title prompt(prompt 作为精确歌词)。为 false 时:只需 prompt,若 instrumental=false 将自动生成歌词。",
117
+ description: "是否启用自定义模式。为 true 时:如果 instrumental=true,仅需提供 style 和 title;如果 instrumental=false,需要提供 style、title,可选择提供 prompt(音乐描述)和/或 lyrics(精确歌词)。为 false 时:只需 prompt,若 instrumental=false 将自动生成歌词。",
109
118
  default: true,
110
119
  },
111
120
  instrumental: {
112
121
  type: "boolean",
113
- description: "是否生成纯音乐(无歌词)。当 customMode=true 且该值=false 时,需要提供精确歌词(使用 prompt 作为歌词);当 customMode=false 且该值=false,将自动生成歌词。",
122
+ description: "是否生成纯音乐(无歌词)。当 customMode=true 且该值=false 时,可以同时提供 prompt(音乐描述)和 lyrics(精确歌词);当 customMode=false 且该值=false,将自动生成歌词。",
114
123
  default: false,
115
124
  },
116
125
  model: {
@@ -131,7 +140,7 @@ const GENERATE_MUSIC_TOOL = {
131
140
  };
132
141
  const COVER_MUSIC_TOOL = {
133
142
  name: "cover_music",
134
- description: "翻唱音乐。上传音频文件并转换为新的风格,保留核心旋律。\n\n注意:\n- 生成需要时间,请每10-30秒轮询调用 query_progress 工具,直到状态为 'complete' 或 'failed'。\n- 平台仅保存音频与封面文件 15 天,请及时下载保存。\n\n返回字段说明:\n- taskId: 任务ID,用于查询进度\n- status: 任务状态 (pending)\n- message: 状态描述",
143
+ description: "翻唱音乐。上传音频文件并转换为新的风格,保留核心旋律。\n\n注意:\n- 生成需要时间,请每10-30秒轮询调用 query_progress 工具,直到状态为 'complete' 或 'failed'。\n- 平台仅保存音频与封面文件 15 天,请及时下载保存。\n- 当 instrumental=false 时,可以同时提供 prompt(风格描述)和 lyrics(精确歌词)。\n\n返回字段说明:\n- taskId: 任务ID,用于查询进度\n- status: 任务状态 (pending)\n- message: 状态描述",
135
144
  inputSchema: {
136
145
  type: "object",
137
146
  properties: {
@@ -141,11 +150,16 @@ const COVER_MUSIC_TOOL = {
141
150
  },
142
151
  prompt: {
143
152
  type: "string",
144
- description: "翻唱风格描述。",
153
+ description: "翻唱风格描述,描述想要的音乐风格、情感、乐器等。当 instrumental=false 时,可与 lyrics 字段配合使用。",
154
+ },
155
+ lyrics: {
156
+ type: "string",
157
+ description: "精确歌词内容,用于 instrumental=false 时。当提供此字段时,AI将按照此歌词演唱。长度限制:V3_5和V4模型3000字符,V4_5模型5000字符。",
158
+ default: "",
145
159
  },
146
160
  style: {
147
161
  type: "string",
148
- description: "目标音乐风格。",
162
+ description: "目标音乐风格,如'古典'、'流行'、'摇滚'、'电子'等。",
149
163
  default: "",
150
164
  },
151
165
  title: {
@@ -155,7 +169,7 @@ const COVER_MUSIC_TOOL = {
155
169
  },
156
170
  instrumental: {
157
171
  type: "boolean",
158
- description: "是否生成纯音乐版本。",
172
+ description: "是否生成纯音乐版本。当该值=false 时,可以同时提供 prompt(风格描述)和 lyrics(精确歌词)。",
159
173
  default: false,
160
174
  },
161
175
  model: {
@@ -166,7 +180,7 @@ const COVER_MUSIC_TOOL = {
166
180
  },
167
181
  negativeTags: {
168
182
  type: "string",
169
- description: "负面标签。",
183
+ description: "负面标签,描述不想要的音乐元素。",
170
184
  default: "",
171
185
  },
172
186
  },
@@ -176,7 +190,7 @@ const COVER_MUSIC_TOOL = {
176
190
  };
177
191
  const EXTEND_MUSIC_TOOL = {
178
192
  name: "extend_music",
179
- description: "扩展音乐。在保留原始音频风格的同时扩展音轨长度。\n\n注意:\n- 生成需要时间,请每10-30秒轮询调用 query_progress 工具,直到状态为 'complete' 或 'failed'。\n- 平台仅保存音频与封面文件 15 天,请及时下载保存。\n\n返回字段说明:\n- taskId: 任务ID,用于查询进度\n- status: 任务状态 (pending)\n- message: 状态描述",
193
+ description: "扩展音乐。在保留原始音频风格的同时扩展音轨长度。\n\n注意:\n- 生成需要时间,请每10-30秒轮询调用 query_progress 工具,直到状态为 'complete' 或 'failed'。\n- 平台仅保存音频与封面文件 15 天,请及时下载保存。\n- 当 instrumental=false 时,可以同时提供 prompt(扩展描述)和 lyrics(精确歌词)。\n\n返回字段说明:\n- taskId: 任务ID,用于查询进度\n- status: 任务状态 (pending)\n- message: 状态描述",
180
194
  inputSchema: {
181
195
  type: "object",
182
196
  properties: {
@@ -186,11 +200,16 @@ const EXTEND_MUSIC_TOOL = {
186
200
  },
187
201
  prompt: {
188
202
  type: "string",
189
- description: "扩展描述,如'用更多舒缓的音符延长音乐'",
203
+ description: "扩展描述,如'用更多舒缓的音符延长音乐'。当 instrumental=false 时,可与 lyrics 字段配合使用。",
204
+ },
205
+ lyrics: {
206
+ type: "string",
207
+ description: "精确歌词内容,用于 instrumental=false 时。当提供此字段时,AI将按照此歌词演唱扩展部分。长度限制:V3_5和V4模型3000字符,V4_5模型5000字符。",
208
+ default: "",
190
209
  },
191
210
  style: {
192
211
  type: "string",
193
- description: "保持的音乐风格。",
212
+ description: "保持的音乐风格,如'古典'、'流行'、'摇滚'、'电子'等。",
194
213
  default: "",
195
214
  },
196
215
  title: {
@@ -205,7 +224,7 @@ const EXTEND_MUSIC_TOOL = {
205
224
  },
206
225
  instrumental: {
207
226
  type: "boolean",
208
- description: "是否生成纯音乐版本。",
227
+ description: "是否生成纯音乐版本。当该值=false 时,可以同时提供 prompt(扩展描述)和 lyrics(精确歌词)。",
209
228
  default: false,
210
229
  },
211
230
  model: {
@@ -216,7 +235,7 @@ const EXTEND_MUSIC_TOOL = {
216
235
  },
217
236
  negativeTags: {
218
237
  type: "string",
219
- description: "负面标签。",
238
+ description: "负面标签,描述不想要的音乐元素。",
220
239
  default: "",
221
240
  },
222
241
  },
@@ -336,7 +355,7 @@ async function makeApiRequestRaw(url, method, body, retries = 3) {
336
355
  }
337
356
  }
338
357
  // 生成音乐处理函数
339
- async function handleGenerateMusic(input) {
358
+ async function handleGenerateMusic(input, chatSessionId) {
340
359
  try {
341
360
  if (!input || typeof input !== 'object') {
342
361
  return {
@@ -345,44 +364,107 @@ async function handleGenerateMusic(input) {
345
364
  errorMessage: "输入参数格式错误,预期为包含prompt字段的对象。",
346
365
  };
347
366
  }
348
- const { prompt, style = "", title = "", customMode = true, instrumental = false, model = "V4_5", negativeTags = "" } = input;
349
- if (!prompt || typeof prompt !== 'string') {
350
- return {
351
- content: [],
352
- isError: true,
353
- errorMessage: "prompt字段是必需的,且必须为字符串类型。",
354
- };
367
+ const { prompt, lyrics = "", style = "", title = "", customMode = true, instrumental = false, model = "V4_5", negativeTags = "" } = input;
368
+ // 根据API文档,当customMode=false时,只需要prompt
369
+ // 当customMode=true且instrumental=true时,只需要style和title
370
+ // 当customMode=true且instrumental=false时,需要style和title,可以选择提供prompt和/或lyrics
371
+ if (!customMode) {
372
+ // 非自定义模式:只需要prompt
373
+ if (!prompt || typeof prompt !== 'string') {
374
+ return {
375
+ content: [],
376
+ isError: true,
377
+ errorMessage: "非自定义模式下,prompt字段是必需的,且必须为字符串类型。",
378
+ };
379
+ }
355
380
  }
356
- // 验证prompt长度
357
- const maxPromptLength = model === "V4_5" ? 5000 : 3000;
358
- if (prompt.length > maxPromptLength) {
359
- return {
360
- content: [],
361
- isError: true,
362
- errorMessage: `prompt长度超过限制。${model}模型最大允许${maxPromptLength}字符。`,
363
- };
381
+ else if (instrumental) {
382
+ // 自定义模式且为纯音乐:只需要style和title
383
+ if (!style || typeof style !== 'string') {
384
+ return {
385
+ content: [],
386
+ isError: true,
387
+ errorMessage: "自定义纯音乐模式下,style字段是必需的,且必须为字符串类型。",
388
+ };
389
+ }
390
+ if (!title || typeof title !== 'string') {
391
+ return {
392
+ content: [],
393
+ isError: true,
394
+ errorMessage: "自定义纯音乐模式下,title字段是必需的,且必须为字符串类型。",
395
+ };
396
+ }
364
397
  }
365
- // 验证style长度
366
- const maxStyleLength = model === "V4_5" ? 1000 : 200;
367
- if (style.length > maxStyleLength) {
368
- return {
369
- content: [],
370
- isError: true,
371
- errorMessage: `style长度超过限制。${model}模型最大允许${maxStyleLength}字符。`,
372
- };
398
+ else {
399
+ // 自定义模式且有歌词:需要style和title,可以选择提供prompt和/或lyrics
400
+ if (!style || typeof style !== 'string') {
401
+ return {
402
+ content: [],
403
+ isError: true,
404
+ errorMessage: "自定义有歌词模式下,style字段是必需的,且必须为字符串类型。",
405
+ };
406
+ }
407
+ if (!title || typeof title !== 'string') {
408
+ return {
409
+ content: [],
410
+ isError: true,
411
+ errorMessage: "自定义有歌词模式下,title字段是必需的,且必须为字符串类型。",
412
+ };
413
+ }
414
+ if (!prompt && !lyrics) {
415
+ return {
416
+ content: [],
417
+ isError: true,
418
+ errorMessage: "自定义有歌词模式下,至少需要提供prompt(音乐描述)或lyrics(精确歌词)中的一个。",
419
+ };
420
+ }
421
+ }
422
+ // 验证prompt长度(如果提供)
423
+ if (prompt) {
424
+ const maxPromptLength = model === "V4_5" ? 5000 : 3000;
425
+ if (prompt.length > maxPromptLength) {
426
+ return {
427
+ content: [],
428
+ isError: true,
429
+ errorMessage: `prompt长度超过限制。${model}模型最大允许${maxPromptLength}字符。`,
430
+ };
431
+ }
432
+ }
433
+ // 验证lyrics长度(如果提供)
434
+ if (lyrics) {
435
+ const maxLyricsLength = model === "V4_5" ? 5000 : 3000;
436
+ if (lyrics.length > maxLyricsLength) {
437
+ return {
438
+ content: [],
439
+ isError: true,
440
+ errorMessage: `lyrics长度超过限制。${model}模型最大允许${maxLyricsLength}字符。`,
441
+ };
442
+ }
443
+ }
444
+ // 验证style长度(如果提供)
445
+ if (style) {
446
+ const maxStyleLength = model === "V4_5" ? 1000 : 200;
447
+ if (style.length > maxStyleLength) {
448
+ return {
449
+ content: [],
450
+ isError: true,
451
+ errorMessage: `style长度超过限制。${model}模型最大允许${maxStyleLength}字符。`,
452
+ };
453
+ }
373
454
  }
374
- // 验证title长度
375
- if (title.length > 80) {
455
+ // 验证title长度(如果提供)
456
+ if (title && title.length > 80) {
376
457
  return {
377
458
  content: [],
378
459
  isError: true,
379
460
  errorMessage: "title长度超过限制,最大允许80字符。",
380
461
  };
381
462
  }
382
- const callbackUrl = buildCallbackUrl("generate");
463
+ const callbackUrl = buildCallbackUrl("generate", chatSessionId);
383
464
  console.error(`使用回调地址: ${callbackUrl}`);
465
+ // 构建请求体,根据API文档,当customMode=true且instrumental=false时,
466
+ // 如果提供了lyrics,则使用lyrics作为prompt字段发送给API
384
467
  const requestBody = {
385
- prompt,
386
468
  style,
387
469
  title,
388
470
  customMode,
@@ -391,6 +473,14 @@ async function handleGenerateMusic(input) {
391
473
  negativeTags,
392
474
  callBackUrl: callbackUrl, // 必需的参数
393
475
  };
476
+ // 根据Suno API文档,当customMode=true且instrumental=false时,
477
+ // 如果提供了精确歌词,应该将其作为prompt字段发送
478
+ if (customMode && !instrumental && lyrics) {
479
+ requestBody.prompt = lyrics;
480
+ }
481
+ else if (prompt) {
482
+ requestBody.prompt = prompt;
483
+ }
394
484
  const result = await makeApiRequest('/generate', 'POST', requestBody);
395
485
  const taskData = {
396
486
  taskId: result.data?.taskId || result.taskId || result.id || result.task_id || "unknown",
@@ -419,7 +509,7 @@ async function handleGenerateMusic(input) {
419
509
  }
420
510
  }
421
511
  // 翻唱音乐处理函数
422
- async function handleCoverMusic(input) {
512
+ async function handleCoverMusic(input, chatSessionId) {
423
513
  try {
424
514
  if (!input || typeof input !== 'object') {
425
515
  return {
@@ -428,7 +518,7 @@ async function handleCoverMusic(input) {
428
518
  errorMessage: "输入参数格式错误,预期为包含uploadUrl和prompt字段的对象。",
429
519
  };
430
520
  }
431
- const { uploadUrl, prompt, style = "", title = "", instrumental = false, model = "V4_5", negativeTags = "" } = input;
521
+ const { uploadUrl, prompt, lyrics = "", style = "", title = "", instrumental = false, model = "V4_5", negativeTags = "" } = input;
432
522
  if (!uploadUrl || typeof uploadUrl !== 'string') {
433
523
  return {
434
524
  content: [],
@@ -443,17 +533,45 @@ async function handleCoverMusic(input) {
443
533
  errorMessage: "prompt字段是必需的,且必须为字符串类型。",
444
534
  };
445
535
  }
536
+ // 验证prompt长度
537
+ if (prompt) {
538
+ const maxPromptLength = model === "V4_5" ? 5000 : 3000;
539
+ if (prompt.length > maxPromptLength) {
540
+ return {
541
+ content: [],
542
+ isError: true,
543
+ errorMessage: `prompt长度超过限制。${model}模型最大允许${maxPromptLength}字符。`,
544
+ };
545
+ }
546
+ }
547
+ // 验证lyrics长度(如果提供)
548
+ if (lyrics) {
549
+ const maxLyricsLength = model === "V4_5" ? 5000 : 3000;
550
+ if (lyrics.length > maxLyricsLength) {
551
+ return {
552
+ content: [],
553
+ isError: true,
554
+ errorMessage: `lyrics长度超过限制。${model}模型最大允许${maxLyricsLength}字符。`,
555
+ };
556
+ }
557
+ }
446
558
  const requestBody = {
447
559
  uploadUrl,
448
- prompt,
449
560
  style,
450
561
  title,
451
562
  customMode: true,
452
563
  instrumental,
453
564
  model,
454
565
  negativeTags,
455
- callBackUrl: buildCallbackUrl("upload-cover"), // 必需的参数
566
+ callBackUrl: buildCallbackUrl("upload-cover", chatSessionId), // 必需的参数
456
567
  };
568
+ // 根据Suno API文档,当instrumental=false且提供了lyrics时,使用lyrics作为prompt字段发送给API
569
+ if (!instrumental && lyrics) {
570
+ requestBody.prompt = lyrics;
571
+ }
572
+ else {
573
+ requestBody.prompt = prompt;
574
+ }
457
575
  const result = await makeApiRequest('/generate/upload-cover', 'POST', requestBody);
458
576
  const taskData = {
459
577
  taskId: result.data?.taskId || result.taskId || result.id || result.task_id || "unknown",
@@ -482,7 +600,7 @@ async function handleCoverMusic(input) {
482
600
  }
483
601
  }
484
602
  // 扩展音乐处理函数
485
- async function handleExtendMusic(input) {
603
+ async function handleExtendMusic(input, chatSessionId) {
486
604
  try {
487
605
  if (!input || typeof input !== 'object') {
488
606
  return {
@@ -491,7 +609,7 @@ async function handleExtendMusic(input) {
491
609
  errorMessage: "输入参数格式错误,预期为包含uploadUrl、prompt和continueAt字段的对象。",
492
610
  };
493
611
  }
494
- const { uploadUrl, prompt, style = "", title = "", continueAt, instrumental = false, model = "V4_5", negativeTags = "" } = input;
612
+ const { uploadUrl, prompt, lyrics = "", style = "", title = "", continueAt, instrumental = false, model = "V4_5", negativeTags = "" } = input;
495
613
  if (!uploadUrl || typeof uploadUrl !== 'string') {
496
614
  return {
497
615
  content: [],
@@ -513,18 +631,46 @@ async function handleExtendMusic(input) {
513
631
  errorMessage: "continueAt字段是必需的,且必须为大于0的数字。",
514
632
  };
515
633
  }
634
+ // 验证prompt长度
635
+ if (prompt) {
636
+ const maxPromptLength = model === "V4_5" ? 5000 : 3000;
637
+ if (prompt.length > maxPromptLength) {
638
+ return {
639
+ content: [],
640
+ isError: true,
641
+ errorMessage: `prompt长度超过限制。${model}模型最大允许${maxPromptLength}字符。`,
642
+ };
643
+ }
644
+ }
645
+ // 验证lyrics长度(如果提供)
646
+ if (lyrics) {
647
+ const maxLyricsLength = model === "V4_5" ? 5000 : 3000;
648
+ if (lyrics.length > maxLyricsLength) {
649
+ return {
650
+ content: [],
651
+ isError: true,
652
+ errorMessage: `lyrics长度超过限制。${model}模型最大允许${maxLyricsLength}字符。`,
653
+ };
654
+ }
655
+ }
516
656
  const requestBody = {
517
657
  uploadUrl,
518
658
  defaultParamFlag: true,
519
659
  instrumental,
520
- prompt,
521
660
  style,
522
661
  title,
523
662
  continueAt,
524
663
  model,
525
664
  negativeTags,
526
- callBackUrl: buildCallbackUrl("upload-extend"), // 必需的参数
665
+ callBackUrl: buildCallbackUrl("upload-extend", chatSessionId), // 必需的参数
527
666
  };
667
+ // 根据Suno API文档,当instrumental=false且提供了lyrics时,使用lyrics作为prompt字段发送给API
668
+ if (!instrumental && lyrics) {
669
+ requestBody.prompt = lyrics;
670
+ }
671
+ else {
672
+ requestBody.prompt = prompt;
673
+ }
528
674
  const result = await makeApiRequest('/generate/upload-extend', 'POST', requestBody);
529
675
  const taskData = {
530
676
  taskId: result.data?.taskId || result.taskId || result.id || result.task_id || "unknown",
@@ -613,6 +759,12 @@ async function handleQueryProgress(input) {
613
759
  status = 'complete';
614
760
  if (status === 'running')
615
761
  status = 'processing';
762
+ // 仅当存在最终音频链接(audioUrl 非空)才认为完成;
763
+ // 如果只有流式地址(streamAudioUrl)但 audioUrl 为空,则视为进行中
764
+ const hasFinalAudio = typeof result.audioUrl === 'string' && result.audioUrl.trim() !== '';
765
+ if (status === 'complete' && !hasFinalAudio) {
766
+ status = 'processing';
767
+ }
616
768
  let statusMessage = "";
617
769
  switch (status) {
618
770
  case 'pending':
@@ -641,7 +793,7 @@ async function handleQueryProgress(input) {
641
793
  status,
642
794
  progress,
643
795
  message: statusMessage,
644
- result: status === 'complete' ? {
796
+ result: status === 'complete' && hasFinalAudio ? {
645
797
  // 基本信息
646
798
  taskId: result.taskId,
647
799
  status: result.status,
@@ -811,14 +963,24 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
811
963
  try {
812
964
  const toolName = request.params.name;
813
965
  const toolInput = request.params.arguments;
966
+ // 解析 meta.chatSessionId (如果存在)
967
+ const chatSessionId = request?.meta?.chatSessionId
968
+ ?? request?.params?.meta?.chatSessionId
969
+ ?? undefined;
970
+ if (chatSessionId) {
971
+ console.error(`接收到 chatSessionId: ${chatSessionId}`);
972
+ }
973
+ else {
974
+ console.error("未在请求中检测到 chatSessionId(meta.chatSessionId)");
975
+ }
814
976
  console.error(`收到工具调用请求: ${toolName}, 输入: ${JSON.stringify(toolInput)}`);
815
977
  switch (toolName) {
816
978
  case GENERATE_MUSIC_TOOL.name:
817
- return await handleGenerateMusic(toolInput);
979
+ return await handleGenerateMusic(toolInput, chatSessionId);
818
980
  case COVER_MUSIC_TOOL.name:
819
- return await handleCoverMusic(toolInput);
981
+ return await handleCoverMusic(toolInput, chatSessionId);
820
982
  case EXTEND_MUSIC_TOOL.name:
821
- return await handleExtendMusic(toolInput);
983
+ return await handleExtendMusic(toolInput, chatSessionId);
822
984
  case QUERY_PROGRESS_TOOL.name:
823
985
  return await handleQueryProgress(toolInput);
824
986
  default:
package/package.json CHANGED
@@ -1,40 +1,40 @@
1
- {
2
- "name": "mcp-ai-music",
3
- "version": "1.0.4",
4
- "description": "AI作曲MCP服务 - 基于Suno4.5 API的音乐生成、翻唱、扩展和进度查询工具",
5
- "main": "dist/index.js",
6
- "scripts": {
7
- "build": "tsc",
8
- "start": "node dist/index.js",
9
- "dev": "ts-node src/index.ts",
10
- "test": "echo \"Error: no test specified\" && exit 1"
11
- },
12
- "keywords": [
13
- "AI",
14
- "music",
15
- "composition",
16
- "suno",
17
- "mcp",
18
- "audio",
19
- "generation"
20
- ],
21
- "author": "",
22
- "license": "ISC",
23
- "bin": {
24
- "mcp-ai-music": "dist/index.js"
25
- },
26
- "files": [
27
- "dist",
28
- "README.md"
29
- ],
30
- "dependencies": {
31
- "@modelcontextprotocol/sdk": "^1.10.0",
32
- "node-fetch": "^2.7.0"
33
- },
34
- "devDependencies": {
35
- "@types/node": "^22.14.1",
36
- "@types/node-fetch": "^2.6.12",
37
- "typescript": "^5.8.3",
38
- "ts-node": "^10.9.2"
39
- }
40
- }
1
+ {
2
+ "name": "mcp-ai-music",
3
+ "version": "1.0.7",
4
+ "description": "AI作曲MCP服务 - 基于Suno4.5 API的音乐生成、翻唱、扩展和进度查询工具",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "start": "node dist/index.js",
9
+ "dev": "ts-node src/index.ts",
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "keywords": [
13
+ "AI",
14
+ "music",
15
+ "composition",
16
+ "suno",
17
+ "mcp",
18
+ "audio",
19
+ "generation"
20
+ ],
21
+ "author": "",
22
+ "license": "ISC",
23
+ "bin": {
24
+ "mcp-ai-music": "dist/index.js"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "README.md"
29
+ ],
30
+ "dependencies": {
31
+ "@modelcontextprotocol/sdk": "^1.10.0",
32
+ "node-fetch": "^2.7.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^22.14.1",
36
+ "@types/node-fetch": "^2.6.12",
37
+ "typescript": "^5.8.3",
38
+ "ts-node": "^10.9.2"
39
+ }
40
+ }