@avclabs.ai/media-mcp 0.2.0 → 0.2.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/README.md +177 -177
- package/README_CN.md +419 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +6 -6
- package/dist/server.js.map +1 -1
- package/dist/video-enhancement.js +37 -37
- package/dist/video-enhancement.js.map +1 -1
- package/package.json +2 -2
- package/README_EN.md +0 -419
package/README_CN.md
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
# media-mcp (Node.js)
|
|
2
|
+
|
|
3
|
+
[English](https://github.com/avclabs/media-mcp/blob/main/README.md) | 中文
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@avclabs.ai/media-mcp)
|
|
6
|
+
[](https://nodejs.org/)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
基于 MCP 协议的视频增强与图像分割服务,作为 MCP Client-Server 与后端 HTTP Server 交互。
|
|
10
|
+
|
|
11
|
+
## 功能
|
|
12
|
+
|
|
13
|
+
提供以下 MCP Tools:
|
|
14
|
+
|
|
15
|
+
**视频增强**
|
|
16
|
+
- `create_task` - 创建视频增强任务(支持 URL 或本地文件上传)
|
|
17
|
+
- `get_task_status` - 查询任务状态
|
|
18
|
+
- `enhance_video_sync` - 同步增强视频(阻塞等待,默认50秒截断)
|
|
19
|
+
|
|
20
|
+
**图像分割 (SAM3)**
|
|
21
|
+
- `sam3_predict` - SAM3 图像分割(支持本地路径、URL 或 Base64 图片)
|
|
22
|
+
|
|
23
|
+
## 前置要求
|
|
24
|
+
|
|
25
|
+
- **Node.js >= 18** (检查:`node --version`)
|
|
26
|
+
- **API Key** (用于身份认证,请联系服务提供方获取)
|
|
27
|
+
|
|
28
|
+
## 懒人安装(推荐)
|
|
29
|
+
|
|
30
|
+
如果你使用的 AI Agent 有确定的 MCP 配置路径,直接复制下面这句发给 AI:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
帮我安装 npm 包 @avclabs.ai/media-mcp 作为 MCP server。我的 API Key 是:sk-xxxxxxxx。
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
AI 会自动完成:
|
|
37
|
+
1. 检测你使用的 MCP 客户端
|
|
38
|
+
2. 找到配置文件路径
|
|
39
|
+
3. 写入正确的配置
|
|
40
|
+
4. 提示你重启客户端
|
|
41
|
+
|
|
42
|
+
## 手动安装
|
|
43
|
+
|
|
44
|
+
无需安装,直接在 MCP 客户端配置中使用 `npx` 运行。
|
|
45
|
+
|
|
46
|
+
### 1. Claude Code(CLI)
|
|
47
|
+
|
|
48
|
+
在 Claude Code 中运行:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
/mcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
查看输出中 **"User MCPs"** 对应的配置文件路径,然后编辑该文件。
|
|
55
|
+
|
|
56
|
+
常见路径(如果 `/mcp` 不可用):
|
|
57
|
+
- **Windows**: `%USERPROFILE%\.claude.json`
|
|
58
|
+
- **macOS**: `~/.claude.json`
|
|
59
|
+
- **Linux**: `~/.claude.json`
|
|
60
|
+
- **旧版/备用**: `~/.claude/mcp.json`
|
|
61
|
+
|
|
62
|
+
粘贴以下内容(将 `your-api-key` 替换为实际 API Key):
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"video-enhancement": {
|
|
68
|
+
"command": "npx",
|
|
69
|
+
"args": ["-y", "@avclabs.ai/media-mcp@latest"],
|
|
70
|
+
"env": {
|
|
71
|
+
"API_KEY": "your-api-key"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
保存后运行 `/mcp` 验证是否加载成功。
|
|
79
|
+
|
|
80
|
+
### 2. Cursor
|
|
81
|
+
|
|
82
|
+
进入 **设置 > Tools & MCPs > Add New MCP Server**:
|
|
83
|
+
|
|
84
|
+
- **Name**:`video-enhancement`
|
|
85
|
+
- **Type**:`command`
|
|
86
|
+
- **Command**:
|
|
87
|
+
```bash
|
|
88
|
+
env API_KEY=your-api-key npx -y @avclabs.ai/media-mcp@latest
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
或编辑 `~/.cursor/mcp.json`:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"mcpServers": {
|
|
96
|
+
"video-enhancement": {
|
|
97
|
+
"command": "npx",
|
|
98
|
+
"args": ["-y", "@avclabs.ai/media-mcp@latest"],
|
|
99
|
+
"env": {
|
|
100
|
+
"API_KEY": "your-api-key"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## 验证安装
|
|
108
|
+
|
|
109
|
+
重启客户端后,确认工具是否加载成功:
|
|
110
|
+
|
|
111
|
+
1. 或直接问 AI:"你有哪些可用的工具?"
|
|
112
|
+
2. 应看到:`create_task`、`get_task_status`、`enhance_video_sync`、`sam3_predict`
|
|
113
|
+
|
|
114
|
+
## 配置项
|
|
115
|
+
|
|
116
|
+
| 变量名 | 必填 | 默认值 | 说明 |
|
|
117
|
+
|---|---|---|---|
|
|
118
|
+
| `API_KEY` | **是** | - | API 认证密钥(视频增强和 SAM3 共用) |
|
|
119
|
+
| `HTTP_API_BASE_URL` | 否 | `https://mcp.avc.ai/enhance` | 视频增强服务接口地址 |
|
|
120
|
+
| `SAM3_API_BASE_URL` | 否 | `https://mcp.avc.ai/sam` | SAM3 服务接口地址 |
|
|
121
|
+
| `SAM3_POLL_INTERVAL` | 否 | `2000` | SAM3 轮询间隔(毫秒) |
|
|
122
|
+
| `SAM3_POLL_MAX_ATTEMPTS` | 否 | `25` | SAM3 最大轮询次数 |
|
|
123
|
+
|
|
124
|
+
### 自定义服务地址
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"env": {
|
|
129
|
+
"HTTP_API_BASE_URL": "https://your-endpoint.com",
|
|
130
|
+
"API_KEY": "your-api-key",
|
|
131
|
+
"SAM3_API_BASE_URL": "https://your-sam3-endpoint.com"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
或通过命令行参数:
|
|
137
|
+
```bash
|
|
138
|
+
npx -y @avclabs.ai/media-mcp@latest --base-url https://your-endpoint.com --api-key your-api-key --sam3-base-url https://your-sam3-endpoint.com
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## 推荐的工作流程
|
|
142
|
+
|
|
143
|
+
本项目提供了**同步**和**异步**两种模式。
|
|
144
|
+
|
|
145
|
+
**由于 MCP Agent 对单个 tool 调用通常有约 60 秒的超时限制**,处理时间较长的任务(视频增强)强烈建议使用**异步模式**:
|
|
146
|
+
|
|
147
|
+
### 异步模式(推荐)
|
|
148
|
+
|
|
149
|
+
**视频增强**:
|
|
150
|
+
1. 调用 `create_task` 创建任务 → 立即获得 `task_id`
|
|
151
|
+
2. 等待几秒后,调用 `get_task_status` 查询状态
|
|
152
|
+
3. 如果 `status` 为 `processing`,继续等待并重复步骤 2
|
|
153
|
+
4. 如果 `status` 为 `completed`,任务完成,结果中包含 `video_url`
|
|
154
|
+
5. 如果 `status` 为 `failed`,任务失败,结果中包含 `error_message`
|
|
155
|
+
|
|
156
|
+
### 同步模式(简单场景)
|
|
157
|
+
|
|
158
|
+
**视频增强**:
|
|
159
|
+
- 调用 `enhance_video_sync` → 服务器内部自动轮询
|
|
160
|
+
- 默认最多等待50秒
|
|
161
|
+
- 如果50秒内完成,直接返回结果
|
|
162
|
+
- 如果50秒未完成,返回 `task_id` 和提示,让 Agent 切换到 `get_task_status` 继续查询
|
|
163
|
+
|
|
164
|
+
**图像分割 (SAM3)**:
|
|
165
|
+
- 调用 `sam3_predict` → 服务器内部自动轮询
|
|
166
|
+
- 默认最多等待50秒(25次 × 2秒轮询间隔)
|
|
167
|
+
- 如果50秒内完成,直接返回分割结果
|
|
168
|
+
- 如果50秒未完成,返回截断提示,告知任务仍在处理中
|
|
169
|
+
|
|
170
|
+
## 使用示例
|
|
171
|
+
|
|
172
|
+
配置完成后,用自然语言对 AI 说:
|
|
173
|
+
|
|
174
|
+
> "帮我把这个视频增强到 1080p:https://example.com/video.mp4"
|
|
175
|
+
|
|
176
|
+
> "把我桌面的 video.mp4 提升到 2k 画质"
|
|
177
|
+
|
|
178
|
+
> "帮我分析这张图片,找出里面的所有物体:C:\\Users\\xxx\\photo.png"
|
|
179
|
+
|
|
180
|
+
> "用 SAM3 分割这张图片,prompt 是 'find all cars'"
|
|
181
|
+
|
|
182
|
+
AI 会根据任务复杂度自动选择同步或异步工具完成任务。
|
|
183
|
+
|
|
184
|
+
## 提供的 Tools
|
|
185
|
+
|
|
186
|
+
### 视频增强
|
|
187
|
+
|
|
188
|
+
#### create_task
|
|
189
|
+
|
|
190
|
+
创建视频增强任务(异步)。
|
|
191
|
+
|
|
192
|
+
> **推荐使用**:适用于绝大多数场景,尤其是时长超过10秒的视频,可避免超时和长时间阻塞连接。
|
|
193
|
+
|
|
194
|
+
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
|
195
|
+
|---|---|---|---|---|
|
|
196
|
+
| `video_source` | string | 是 | - | 视频 URL 或本地文件路径(URL 必须公网可访问,不支持需要登录或签名的链接) |
|
|
197
|
+
| `type` | string | 否 | `url` | `url` 或 `local` |
|
|
198
|
+
| `resolution` | string | 否 | `720p` | `480p`、`540p`、`720p`、`1080p`、`2k` |
|
|
199
|
+
|
|
200
|
+
**返回值:**
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"success": true,
|
|
204
|
+
"task_id": "xxx",
|
|
205
|
+
"status": "processing"
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### get_task_status
|
|
210
|
+
|
|
211
|
+
查询视频增强任务状态。
|
|
212
|
+
|
|
213
|
+
> 返回值中的 `status` 字段可能为:`processing`(处理中)、`completed`(已完成)、`failed`(失败)。如果 `status` 为 `processing`,你需要等待几秒后再次调用此工具轮询。
|
|
214
|
+
|
|
215
|
+
| 参数 | 类型 | 必填 |
|
|
216
|
+
|---|---|---|
|
|
217
|
+
| `task_id` | string | 是 |
|
|
218
|
+
|
|
219
|
+
**返回值:**
|
|
220
|
+
```json
|
|
221
|
+
{
|
|
222
|
+
"success": true,
|
|
223
|
+
"task_id": "xxx",
|
|
224
|
+
"status": "completed",
|
|
225
|
+
"progress": 100,
|
|
226
|
+
"video_url": "https://...",
|
|
227
|
+
"message": "Task is still processing, please check again later"
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
其中 `message` 字段只在 `status` 为 `processing` 时出现,提示 Agent 继续等待。
|
|
232
|
+
|
|
233
|
+
#### enhance_video_sync
|
|
234
|
+
|
|
235
|
+
同步增强视频(阻塞等待完成)。
|
|
236
|
+
|
|
237
|
+
> **仅适合短视频(预计处理时间 < 1 分钟)。** 如果任务在50秒内未完成,工具会提前返回并包含 `task_id`,你需要使用 `get_task_status` 继续查询。
|
|
238
|
+
|
|
239
|
+
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
|
240
|
+
|---|---|---|---|---|
|
|
241
|
+
| `video_source` | string | 是 | - | 视频 URL 或本地文件路径 |
|
|
242
|
+
| `type` | string | 否 | `url` | `url` 或 `local` |
|
|
243
|
+
| `resolution` | string | 否 | `720p` | 目标分辨率 |
|
|
244
|
+
| `poll_interval` | number | 否 | `5` | 轮询间隔(秒) |
|
|
245
|
+
| `timeout` | number | 否 | `50` | 同步等待超时时间(秒),超过后主动返回 |
|
|
246
|
+
|
|
247
|
+
**截断返回示例(50秒未完成):**
|
|
248
|
+
```json
|
|
249
|
+
{
|
|
250
|
+
"success": true,
|
|
251
|
+
"status": "processing",
|
|
252
|
+
"task_id": "xxx",
|
|
253
|
+
"message": "Task is still processing (waited 50 seconds). Please use get_task_status to continue polling.",
|
|
254
|
+
"note": "The synchronous wait for this long-running task has been truncated. Switch to get_task_status polling."
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 图像分割 (SAM3)
|
|
259
|
+
|
|
260
|
+
#### sam3_predict
|
|
261
|
+
|
|
262
|
+
使用 SAM3 分割 API 分析图片,生成推理结果(masks、boxes、scores)。
|
|
263
|
+
|
|
264
|
+
**参数:**
|
|
265
|
+
|
|
266
|
+
图片输入(三选一,必须提供其中一种):
|
|
267
|
+
|
|
268
|
+
- `imagePath` (string): 本地图片的绝对路径。支持常见图片格式(如 PNG、JPG、JPEG)。
|
|
269
|
+
- 示例:`"C:\\Users\\xxx\\photo.png"`、`"/home/user/images/cat.jpg"`
|
|
270
|
+
- 适用场景:用户明确提供了本地文件路径
|
|
271
|
+
|
|
272
|
+
- `imageUrl` (string): 公开可访问的图片 URL。
|
|
273
|
+
- 示例:`"https://example.com/photo.jpg"`
|
|
274
|
+
- 适用场景:图片已在网上,用户提供了链接
|
|
275
|
+
- 注意:URL 必须公开可访问,不支持需要登录或签名的链接
|
|
276
|
+
|
|
277
|
+
- `imageBase64` (string): Base64 编码的图片数据。
|
|
278
|
+
- 示例:`"iVBORw0KGgoAAAANSUhEUgAA..."`
|
|
279
|
+
- 适用场景:用户拖拽或上传了图片附件,Agent 将图片编码为 base64 后传入
|
|
280
|
+
- 注意:大图片的 base64 数据会比较大,传输时间可能稍长
|
|
281
|
+
|
|
282
|
+
其他参数:
|
|
283
|
+
|
|
284
|
+
- `prompt` (string, required): 英文文本提示,用于指定要在图片中分割的目标物体。由于 SAM3 模型仅接受英文 prompt,建议传入英文描述。如果用户提供中文或其他非英文文本,Agent 会自动翻译为英文后调用。
|
|
285
|
+
|
|
286
|
+
**正常完成返回:**
|
|
287
|
+
|
|
288
|
+
推理完成后,直接返回 JSON 字符串。该 JSON 包含以下三个字段:
|
|
289
|
+
|
|
290
|
+
- **`masks`**:二维数组。每个元素是一个与输入图片尺寸相同的二值掩码(取值为 0 或 1),用于标记检测到的物体在图片中的像素级位置。数组中的第 i 个掩码对应第 i 个检测到的物体实例。
|
|
291
|
+
- **`boxes`**:二维数组。每个元素是 `[x1, y1, x2, y2]` 格式的边界框坐标,表示检测到的物体在图片中的矩形区域。`x1`、`y1` 为左上角坐标,`x2`、`y2` 为右下角坐标。
|
|
292
|
+
|
|
293
|
+
坐标系说明:以图片左上角为原点 `(0, 0)`,`x` 轴向右增长,`y` 轴向下增长,单位为像素。例如 `[120, 80, 300, 450]` 表示该物体区域从图片左边缘向右 120px、上边缘向下 80px 处开始,延伸至左边缘向右 300px、上边缘向下 450px 处结束,区域宽度为 `x2 - x1 = 180px`,高度为 `y2 - y1 = 370px`。
|
|
294
|
+
- **`scores`**:一维数组。每个元素是对应检测结果的置信度分数,取值范围为 0 到 1。分数越高,表示模型对该检测结果的把握越大。
|
|
295
|
+
|
|
296
|
+
结果 JSON 内容示例:
|
|
297
|
+
|
|
298
|
+
```json
|
|
299
|
+
{
|
|
300
|
+
"masks": [
|
|
301
|
+
[[0, 0, 1, ...], [0, 1, 1, ...], ...],
|
|
302
|
+
[[0, 0, 0, ...], [0, 0, 1, ...], ...]
|
|
303
|
+
],
|
|
304
|
+
"boxes": [
|
|
305
|
+
[120, 80, 300, 450],
|
|
306
|
+
[400, 200, 600, 500]
|
|
307
|
+
],
|
|
308
|
+
"scores": [0.95, 0.87]
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**截断返回示例(50秒未完成):**
|
|
313
|
+
```json
|
|
314
|
+
{
|
|
315
|
+
"success": true,
|
|
316
|
+
"status": "processing",
|
|
317
|
+
"task_id": "xxx",
|
|
318
|
+
"message": "Task is still processing (waited about 50 seconds). Please retry later or record this task_id for manual follow-up.",
|
|
319
|
+
"note": "The synchronous wait for this long-running task has been truncated."
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## 常见问题
|
|
324
|
+
|
|
325
|
+
### Agent 调用工具时报超时怎么办?
|
|
326
|
+
|
|
327
|
+
这是本项目重点解决的问题。MCP Agent(如 Claude、Cursor)对单个 tool 调用通常有约 60 秒的超时限制。如果任务处理时间超过此限制,Agent 会报错并切断连接。
|
|
328
|
+
|
|
329
|
+
**解决方案:**
|
|
330
|
+
|
|
331
|
+
1. **优先使用异步工具**:对于视频增强等耗时任务,始终使用 `create_task` + `get_task_status`。这些工具每次调用都是瞬间返回的,不会触发超时。
|
|
332
|
+
|
|
333
|
+
2. **同步工具的截断机制**:`enhance_video_sync` 已在内部设置了50秒的截断限制。如果任务未在50秒内完成,工具会主动返回 `task_id`,并提示 Agent 使用 `get_task_status` 继续跟进。
|
|
334
|
+
|
|
335
|
+
3. **SAM3 的截断机制**:`sam3_predict` 默认轮询25次(约50秒),如果任务未完成会返回截断提示,告知任务仍在处理中。
|
|
336
|
+
|
|
337
|
+
4. **调整 SAM3 轮询参数**(高级):如果你确定 SAM3 任务通常很快(例如10秒内),可以通过环境变量增加轮询次数:
|
|
338
|
+
```bash
|
|
339
|
+
SAM3_POLL_MAX_ATTEMPTS=60
|
|
340
|
+
```
|
|
341
|
+
但请确保总等待时间不超过 Agent 的超时限制。
|
|
342
|
+
|
|
343
|
+
### 拖拽附件后提示找不到文件?
|
|
344
|
+
|
|
345
|
+
这是 stdio MCP 的已知限制。当通过 Agent 界面拖拽或上传附件时,文件路径通常不会自动传给 MCP Server。
|
|
346
|
+
|
|
347
|
+
**解决方法:**
|
|
348
|
+
|
|
349
|
+
1. **同时提供路径**(推荐):拖拽图片后,在文字中补充说明图片的本地绝对路径:
|
|
350
|
+
> "请处理这张图片 `D:\\photos\\cat.jpg`,找出里面的猫"
|
|
351
|
+
|
|
352
|
+
2. **等待自动编码**:Claude 可能会自动将图片编码为 base64 传入。如果成功,无需额外操作。
|
|
353
|
+
|
|
354
|
+
3. **回答路径询问**:如果 Claude 询问图片路径,直接回复本地绝对路径即可。
|
|
355
|
+
|
|
356
|
+
### 三种输入方式有优先级吗?
|
|
357
|
+
|
|
358
|
+
没有严格优先级。Claude 会根据对话上下文自动选择最合适的方式:
|
|
359
|
+
|
|
360
|
+
- 你提供了本地路径 → 使用 `imagePath`
|
|
361
|
+
- 你提供了网络链接 → 使用 `imageUrl`
|
|
362
|
+
- 你拖拽了附件且没有路径 → 尝试 `imageBase64`
|
|
363
|
+
|
|
364
|
+
### 支持哪些图片格式?
|
|
365
|
+
|
|
366
|
+
支持常见格式:PNG、JPG、JPEG、BMP、WebP 等。建议优先使用 PNG 或 JPG。
|
|
367
|
+
|
|
368
|
+
### URL 图片下载失败怎么办?
|
|
369
|
+
|
|
370
|
+
确保 URL 是**公开可访问**的,不需要登录、Cookie 或签名。如果图片在需要身份验证的服务上(如私有 S3 Bucket、需要登录的图床),请先下载到本地再使用 `imagePath`。
|
|
371
|
+
|
|
372
|
+
### Base64 图片太大怎么办?
|
|
373
|
+
|
|
374
|
+
如果图片很大(如 4K 分辨率),base64 编码后的数据会非常大,可能导致传输变慢。建议:
|
|
375
|
+
|
|
376
|
+
1. 使用 `imagePath` 代替
|
|
377
|
+
2. 或先将图片压缩后再编码
|
|
378
|
+
|
|
379
|
+
## 文件上传说明
|
|
380
|
+
|
|
381
|
+
当 `type` 为 `"local"` 时,MCP Server 会:
|
|
382
|
+
1. 读取本地文件
|
|
383
|
+
2. 通过预签名 URL 直传到 TOS 对象存储
|
|
384
|
+
3. **最大文件大小:100MB**
|
|
385
|
+
|
|
386
|
+
## 故障排查
|
|
387
|
+
|
|
388
|
+
### "command not found: npx"
|
|
389
|
+
|
|
390
|
+
安装 Node.js >= 18:https://nodejs.org/
|
|
391
|
+
|
|
392
|
+
### "Error: --api-key argument or API_KEY environment variable is required"
|
|
393
|
+
|
|
394
|
+
API Key 缺失,请检查配置中的 `env.API_KEY`。
|
|
395
|
+
|
|
396
|
+
### MCP Server 在客户端显示红色/错误
|
|
397
|
+
|
|
398
|
+
查看日志:
|
|
399
|
+
- **Claude Desktop macOS**:`~/Library/Logs/Claude/mcp*.log`
|
|
400
|
+
- **Claude Desktop Windows**:`%APPDATA%\Claude\logs\mcp*.log`
|
|
401
|
+
- **Cursor**:Output 面板 > MCP
|
|
402
|
+
|
|
403
|
+
### "TOS 上传失败"
|
|
404
|
+
|
|
405
|
+
通常是签名不匹配,请确认 `HTTP_API_BASE_URL` 和 `API_KEY` 正确且有效。
|
|
406
|
+
|
|
407
|
+
## 全局安装(可选)
|
|
408
|
+
|
|
409
|
+
如果你不想每次都用 `npx`:
|
|
410
|
+
|
|
411
|
+
```bash
|
|
412
|
+
npm install -g @avclabs.ai/media-mcp
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
然后在配置中使用 `"command": "media-mcp"` 配合 `"args": ["--api-key", "your-api-key"]` 。
|
|
416
|
+
|
|
417
|
+
## License
|
|
418
|
+
|
|
419
|
+
MIT License - 详见 [LICENSE](LICENSE) 文件
|
package/dist/server.d.ts
CHANGED
package/dist/server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* MCP Server -
|
|
3
|
+
* MCP Server - Video enhancement and SAM3 image segmentation
|
|
4
4
|
*/
|
|
5
5
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
6
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
@@ -32,7 +32,7 @@ function loadConfig() {
|
|
|
32
32
|
}
|
|
33
33
|
return { baseUrl: 'https://mcp.avc.ai/enhance', sam3BaseUrl: 'https://mcp.avc.ai/sam' };
|
|
34
34
|
}
|
|
35
|
-
//
|
|
35
|
+
// Main entry
|
|
36
36
|
async function main() {
|
|
37
37
|
const args = process.argv.slice(2);
|
|
38
38
|
const config = loadConfig();
|
|
@@ -42,7 +42,7 @@ async function main() {
|
|
|
42
42
|
let sam3ApiKey = apiKey;
|
|
43
43
|
let sam3PollInterval = parseInt(process.env.SAM3_POLL_INTERVAL || '2000', 10);
|
|
44
44
|
let sam3PollMaxAttempts = parseInt(process.env.SAM3_POLL_MAX_ATTEMPTS || '25', 10);
|
|
45
|
-
//
|
|
45
|
+
// Parse command line arguments
|
|
46
46
|
for (let i = 0; i < args.length; i++) {
|
|
47
47
|
if (args[i] === '--base-url' && i + 1 < args.length) {
|
|
48
48
|
baseUrl = args[i + 1];
|
|
@@ -66,7 +66,7 @@ async function main() {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
if (!apiKey) {
|
|
69
|
-
console.error('
|
|
69
|
+
console.error('Error: --api-key argument or API_KEY environment variable is required');
|
|
70
70
|
process.exit(1);
|
|
71
71
|
}
|
|
72
72
|
const server = new McpServer({
|
|
@@ -77,9 +77,9 @@ async function main() {
|
|
|
77
77
|
tools: {},
|
|
78
78
|
},
|
|
79
79
|
});
|
|
80
|
-
//
|
|
80
|
+
// Register video enhancement tools
|
|
81
81
|
setupVideoEnhancementTools(server, baseUrl, apiKey);
|
|
82
|
-
//
|
|
82
|
+
// Register SAM3 tools
|
|
83
83
|
setupSam3Tools(server, sam3BaseUrl, sam3ApiKey, sam3PollInterval, sam3PollMaxAttempts);
|
|
84
84
|
const transport = new StdioServerTransport();
|
|
85
85
|
await server.connect(transport);
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;AAE3C,SAAS,UAAU;IACjB,MAAM,WAAW,GAAG;QAClB,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,CAAC;QAC1C,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,aAAa,CAAC;KAC7C,CAAC;IACF,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9B,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;gBAChE,OAAO;oBACL,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,4BAA4B;oBACvD,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,wBAAwB;iBAC5D,CAAC;YACJ,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,4BAA4B,EAAE,WAAW,EAAE,wBAAwB,EAAE,CAAC;AAC1F,CAAC;AAED,
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,19 +5,19 @@ import { z } from 'zod';
|
|
|
5
5
|
import FormData from 'form-data';
|
|
6
6
|
// Schemas
|
|
7
7
|
const CreateTaskSchema = z.object({
|
|
8
|
-
video_source: z.string().describe('
|
|
9
|
-
type: z.enum(['url', 'local']).default('url').describe('
|
|
10
|
-
resolution: z.enum(['480p', '540p', '720p', '1080p', '2k']).default('720p').describe('
|
|
8
|
+
video_source: z.string().describe('Video URL or local file path (URL must be publicly accessible, login or signed links are not supported)'),
|
|
9
|
+
type: z.enum(['url', 'local']).default('url').describe('Upload type: url=remote video, local=local file'),
|
|
10
|
+
resolution: z.enum(['480p', '540p', '720p', '1080p', '2k']).default('720p').describe('Target resolution, default 720p'),
|
|
11
11
|
});
|
|
12
12
|
const GetTaskStatusSchema = z.object({
|
|
13
|
-
task_id: z.string().describe('
|
|
13
|
+
task_id: z.string().describe('Task ID'),
|
|
14
14
|
});
|
|
15
15
|
const EnhanceVideoSyncSchema = z.object({
|
|
16
|
-
video_source: z.string().describe('
|
|
17
|
-
type: z.enum(['url', 'local']).default('url').describe('
|
|
18
|
-
resolution: z.enum(['480p', '540p', '720p', '1080p', '2k']).default('720p').describe('
|
|
19
|
-
poll_interval: z.number().default(5).describe('
|
|
20
|
-
timeout: z.number().default(50).describe('
|
|
16
|
+
video_source: z.string().describe('Video URL or local file path (URL must be publicly accessible, login or signed links are not supported)'),
|
|
17
|
+
type: z.enum(['url', 'local']).default('url').describe('Upload type: url=remote video, local=local file'),
|
|
18
|
+
resolution: z.enum(['480p', '540p', '720p', '1080p', '2k']).default('720p').describe('Target resolution, default 720p'),
|
|
19
|
+
poll_interval: z.number().default(5).describe('Polling interval in seconds, default 5'),
|
|
20
|
+
timeout: z.number().default(50).describe('Synchronous wait timeout in seconds, default 50. Returns task_id early when exceeded, use get_task_status to continue polling'),
|
|
21
21
|
});
|
|
22
22
|
export function setupVideoEnhancementTools(server, baseUrl, apiKey) {
|
|
23
23
|
const client = axios.create({
|
|
@@ -29,18 +29,18 @@ export function setupVideoEnhancementTools(server, baseUrl, apiKey) {
|
|
|
29
29
|
timeout: 60000,
|
|
30
30
|
});
|
|
31
31
|
// create_task tool
|
|
32
|
-
server.tool('create_task',
|
|
32
|
+
server.tool('create_task', `Create a video enhancement task (asynchronous).
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
1. URL
|
|
36
|
-
2.
|
|
34
|
+
Two upload methods are supported:
|
|
35
|
+
1. URL upload: provide a video URL
|
|
36
|
+
2. Local upload: provide a local file path, the MCP Server will auto-upload to TOS object storage
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
- video_source:
|
|
40
|
-
- type: "url"
|
|
41
|
-
- resolution:
|
|
38
|
+
Parameters:
|
|
39
|
+
- video_source: video URL or local file path
|
|
40
|
+
- type: "url" or "local"
|
|
41
|
+
- resolution: target resolution
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
After creating a task, a task_id is returned immediately. Use get_task_status to poll for results until status becomes "completed" or "failed".`, CreateTaskSchema.shape, async (args) => {
|
|
44
44
|
try {
|
|
45
45
|
const result = await createTask(client, args.video_source, args.type, args.resolution);
|
|
46
46
|
return {
|
|
@@ -55,7 +55,7 @@ export function setupVideoEnhancementTools(server, baseUrl, apiKey) {
|
|
|
55
55
|
}
|
|
56
56
|
});
|
|
57
57
|
// get_task_status tool
|
|
58
|
-
server.tool('get_task_status', '
|
|
58
|
+
server.tool('get_task_status', 'Query video enhancement task status. The status field can be: processing, completed, or failed. If status is processing, wait a few seconds and call this tool again to poll.', GetTaskStatusSchema.shape, async (args) => {
|
|
59
59
|
try {
|
|
60
60
|
const result = await getTaskStatus(client, args.task_id);
|
|
61
61
|
return {
|
|
@@ -70,20 +70,20 @@ export function setupVideoEnhancementTools(server, baseUrl, apiKey) {
|
|
|
70
70
|
}
|
|
71
71
|
});
|
|
72
72
|
// enhance_video_sync tool
|
|
73
|
-
server.tool('enhance_video_sync',
|
|
73
|
+
server.tool('enhance_video_sync', `Synchronously enhance video (blocks until completion).
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
1. URL
|
|
77
|
-
2.
|
|
75
|
+
Two upload methods are supported:
|
|
76
|
+
1. URL upload: provide a video URL
|
|
77
|
+
2. Local upload: provide a local file path, the MCP Server will auto-upload to TOS object storage
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
- video_source:
|
|
81
|
-
- type: "url"
|
|
82
|
-
- resolution:
|
|
83
|
-
- poll_interval:
|
|
84
|
-
- timeout:
|
|
79
|
+
Parameters:
|
|
80
|
+
- video_source: video URL or local file path
|
|
81
|
+
- type: "url" or "local"
|
|
82
|
+
- resolution: target resolution
|
|
83
|
+
- poll_interval: polling interval in seconds
|
|
84
|
+
- timeout: synchronous wait timeout in seconds, default 50
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
Best for short videos (estimated processing time < 1 minute). If the task is not completed within 50 seconds, the tool returns early with a task_id. Use get_task_status to continue polling.`, EnhanceVideoSyncSchema.shape, async (args) => {
|
|
87
87
|
try {
|
|
88
88
|
const result = await enhanceVideoSync(client, args.video_source, args.type, args.resolution, args.poll_interval, args.timeout);
|
|
89
89
|
return {
|
|
@@ -100,12 +100,12 @@ export function setupVideoEnhancementTools(server, baseUrl, apiKey) {
|
|
|
100
100
|
}
|
|
101
101
|
function checkLocalFile(filePath) {
|
|
102
102
|
if (!fs.existsSync(filePath)) {
|
|
103
|
-
throw new Error(
|
|
103
|
+
throw new Error(`File does not exist: ${filePath}`);
|
|
104
104
|
}
|
|
105
105
|
const stats = fs.statSync(filePath);
|
|
106
106
|
const maxSize = 100 * 1024 * 1024;
|
|
107
107
|
if (stats.size > maxSize) {
|
|
108
|
-
throw new Error('
|
|
108
|
+
throw new Error('File size exceeds 100MB limit');
|
|
109
109
|
}
|
|
110
110
|
return { filePath, fileName: path.basename(filePath) };
|
|
111
111
|
}
|
|
@@ -116,7 +116,7 @@ async function getTosSignature(client, fileName) {
|
|
|
116
116
|
});
|
|
117
117
|
const data = response.data;
|
|
118
118
|
if (data.code !== 0 && data.code !== 200) {
|
|
119
|
-
throw new Error(data.message || '
|
|
119
|
+
throw new Error(data.message || 'Failed to get TOS signature');
|
|
120
120
|
}
|
|
121
121
|
return data.data;
|
|
122
122
|
}
|
|
@@ -153,7 +153,7 @@ async function uploadToTos(filePath, signatureData) {
|
|
|
153
153
|
fileName,
|
|
154
154
|
fileSize: fs.statSync(filePath).size,
|
|
155
155
|
}, null, 2);
|
|
156
|
-
throw new Error(`TOS
|
|
156
|
+
throw new Error(`TOS upload failed: ${response.status} ${response.statusText}\nDebug info: ${debugInfo}`);
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
function parseFileIdFromUrl(url) {
|
|
@@ -199,7 +199,7 @@ async function getTaskStatus(client, taskId) {
|
|
|
199
199
|
error_message: result.error_message,
|
|
200
200
|
created_at: result.created_at,
|
|
201
201
|
updated_at: result.updated_at,
|
|
202
|
-
message: result.status === 'processing' ? '
|
|
202
|
+
message: result.status === 'processing' ? 'Task is still processing, please check again later' : undefined,
|
|
203
203
|
};
|
|
204
204
|
}
|
|
205
205
|
async function enhanceVideoSync(client, videoSource, sourceType, resolution, pollInterval, timeout) {
|
|
@@ -223,8 +223,8 @@ async function enhanceVideoSync(client, videoSource, sourceType, resolution, pol
|
|
|
223
223
|
success: true,
|
|
224
224
|
status: 'processing',
|
|
225
225
|
task_id: taskId,
|
|
226
|
-
message:
|
|
227
|
-
note: '
|
|
226
|
+
message: `Task is still processing (waited ${timeout} seconds). Please use get_task_status to continue polling.`,
|
|
227
|
+
note: 'The synchronous wait for this long-running task has been truncated. Switch to get_task_status polling.',
|
|
228
228
|
};
|
|
229
229
|
}
|
|
230
230
|
await sleep(pollInterval * 1000);
|