vision-mcp
# Vision MCP Server
基于 VLM(视觉语言模型)的 MCP 服务,提供视觉问答、图像解读、目标检测、OCR 以及完整的图像处理工具链。通过 OpenAI 兼容 API 接入任意 VLM 大模型,以 stdio 方势提供服务。
## 特性
- **4 个 VLM 工具**:视觉问答、视觉解读、目标检测(归一化包围盒)、OCR 文字提取
- **8 个图像处理工具**:元信息、缩放、裁剪、旋转、镜像、拼接、画方框、写文字(中文)
- **忠实还原**:所有 VLM 调用内置"不脑补"指令 + `temperature: 0`
- **自动缩放**:VLM 工具内置 `max_dimension` 参数,发送前自动等比缩放,避免超限
- **坐标归一化**:检测工具通过系统提示词 + 后处理双重保障,始终返回 0-1 归一化包围盒
- **双输入模式**:支持本地文件路径和 URL 两种图片输入方式
- **双输出模式**:图像处理工具返回 base64 图片内容,可选 `output_path` 保存到文件
## 快速开始
### 环境要求
- Node.js >= 18
- 任意 OpenAI 兼容的 VLM API(如 OpenAI GPT-4o、Qwen-VL、GLM-4V 等)
### 安装
```bash
git clone <repo-url>
cd vision-mcp
npm install
npm run build
```
### 配置
通过环境变量配置:
| 环境变量 | 必需 | 说明 | 示例 |
|---------|------|------|------|
| `VLM_BASE_URL` | 是 | VLM API 基础地址 | `https://api.openai.com/v1` |
| `VLM_API_KEY` | 是 | API 密钥 | `sk-xxxx` |
| `VLM_MODEL_ID` | 是 | 模型 ID | `gpt-4o` |
| `VISION_MCP_FONT_PATH` | 否 | 中文字体文件路径(.ttf/.otf) | `fonts/SimHei.ttf` |
### 启动
```bash
VLM_BASE_URL=https://api.openai.com/v1 \
VLM_API_KEY=sk-xxxx \
VLM_MODEL_ID=gpt-4o \
node dist/index.js
```
### 在 MCP 客户端中配置
**Claude Desktop** (`claude_desktop_config.json`):
```json
{
"mcpServers": {
"vision": {
"command": "node",
"args": ["/path/to/vision-mcp/dist/index.js"],
"env": {
"VLM_BASE_URL": "https://api.openai.com/v1",
"VLM_API_KEY": "sk-xxxx",
"VLM_MODEL_ID": "gpt-4o",
"VISION_MCP_FONT_PATH": "/path/to/vision-mcp/fonts/SimHei.ttf"
}
}
}
}
```
**Cursor / 其他 MCP 客户端**:参照各客户端文档,使用 `node dist/index.js` 作为启动命令,传入上述环境变量。
### 使用 MCP Inspector 调试
```bash
VLM_BASE_URL=... VLM_API_KEY=... VLM_MODEL_ID=... \
npm run inspector
```
## 工具列表
### VLM 工具(4 个)
通过 OpenAI 兼容 API 调用 VLM 大模型完成视觉任务。所有 VLM 工具:
- 接受 `images`(路径或 URL 数组,1-8 张)
- 内置 `max_dimension`(默认 2048)自动缩放
- 使用 `temperature: 0` + 忠实性提示词确保不脑补
#### `vision_qa` — 视觉问答
对图片提问,返回基于图片内容的文字回答。适用于检查 Web 页面、PPT 页面是否符合要求等场景。
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|------|------|------|--------|------|
| `images` | `string[]` | 是 | — | 图片路径或 URL 列表 |
| `question` | `string` | 是 | — | 要提问的问题 |
| `max_dimension` | `number` | 否 | 2048 | 发送前自动缩放最大边长,设 0 禁用 |
#### `vision_describe` — 视觉解读
详细、真实地解读图片内容,不推测或脑补。
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|------|------|------|--------|------|
| `images` | `string[]` | 是 | — | 图片路径或 URL 列表 |
| `detail_level` | `"brief"\|"normal"\|"detailed"` | 否 | `"normal"` | 描述详细程度 |
| `max_dimension` | `number` | 否 | 2048 | 发送前自动缩放最大边长 |
#### `vision_detect` — 视觉检测
在图片中检测指定目标,返回 0-1 归一化包围盒。
系统提示词强制要求归一化坐标;后处理函数自动检测像素坐标(值 > 1)并除以图片尺寸归一化,双重保障。
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|------|------|------|--------|------|
| `images` | `string[]` | 是 | — | 图片路径或 URL 列表 |
| `target` | `string` | 是 | — | 要检测的目标描述 |
| `max_dimension` | `number` | 否 | 2048 | 发送前自动缩放最大边长 |
返回结构:
```json
{
"detections": [
{
"label": "对象描述",
"bbox": { "x_min": 0.1, "y_min": 0.2, "x_max": 0.3, "y_max": 0.4 },
"confidence": 0.95
}
]
}
```
#### `vision_ocr` — 视觉 OCR
提取图片中所有可见文字。
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|------|------|------|--------|------|
| `images` | `string[]` | 是 | — | 图片路径或 URL 列表 |
| `max_dimension` | `number` | 否 | 2048 | 发送前自动缩放最大边长 |
返回结构:
```json
{
"text_blocks": [
{ "text": "文字内容", "bbox": { "x_min": 0.1, "y_min": 0.2, "x_max": 0.3, "y_max": 0.4 } }
],
"full_text": "所有文字按阅读顺序拼接"
}
```
### 图像处理工具(8 个)
基于 sharp 和 @napi-rs/canvas 的程序化图像操作。所有工具:
- 返回 base64 PNG 图片内容(MCP `image` content type)
- 提供 `structuredContent`(宽高、格式、大小)
- 支持可选 `output_path` 参数保存到文件
#### `image_get_metadata` — 获取图片元信息
返回宽度、高度、格式、通道数、色彩空间、DPI、Alpha 通道、EXIF 方向等。
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `image` | `string` | 是 | 图片路径或 URL |
#### `image_resize` — 图片缩放
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|------|------|------|--------|------|
| `image` | `string` | 是 | — | 图片路径或 URL |
| `width` | `number` | 否 | — | 目标宽度(像素) |
| `height` | `number` | 否 | — | 目标高度(像素) |
| `scale` | `number` | 否 | — | 缩放比例(0.01-10) |
| `fit` | `string` | 否 | `"inside"` | 缩放模式:cover/contain/fill/inside/outside |
| `output_path` | `string` | 否 | — | 保存路径 |
> `width`/`height` 和 `scale` 三选一。`width`/`height` 同时指定时按 `fit` 模式处理。
#### `image_crop` — 图片裁剪
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|------|------|------|--------|------|
| `image` | `string` | 是 | — | 图片路径或 URL |
| `left` | `number` | 是 | — | 裁剪区域左上角 x 坐标 |
| `top` | `number` | 是 | — | 裁剪区域左上角 y 坐标 |
| `width` | `number` | 是 | — | 裁剪区域宽度 |
| `height` | `number` | 是 | — | 裁剪区域高度 |
| `normalized` | `boolean` | 否 | `false` | 坐标是否为 0-1 归一化值 |
| `output_path` | `string` | 否 | — | 保存路径 |
#### `image_rotate` — 图片旋转
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `image` | `string` | 是 | 图片路径或 URL |
| `angle` | `number` | 是 | 旋转角度(正数为顺时针) |
| `output_path` | `string` | 否 | 保存路径 |
#### `image_flip` — 图片镜像
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `image` | `string` | 是 | 图片路径或 URL |
| `direction` | `"horizontal"\|"vertical"` | 是 | horizontal=左右翻转,vertical=上下翻转 |
| `output_path` | `string` | 否 | 保存路径 |
#### `image_concat` — 图片拼接
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|------|------|------|--------|------|
| `images` | `string[]` | 是 | — | 图片路径或 URL 列表 |
| `layout` | `"horizontal"\|"vertical"\|"grid"` | 是 | — | 拼接布局 |
| `cols` | `number` | 否 | — | grid 布局的列数 |
| `gap` | `number` | 否 | 0 | 图片间距(像素) |
| `background` | `string` | 否 | `"#FFFFFF"` | 背景色 |
| `output_path` | `string` | 否 | — | 保存路径 |
#### `image_draw_box` — 添加方框标记
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|------|------|------|--------|------|
| `image` | `string` | 是 | — | 图片路径或 URL |
| `boxes` | `object[]` | 是 | — | 方框列表 |
| `boxes[].x` | `number` | 是 | — | 方框左上角 x 坐标 |
| `boxes[].y` | `number` | 是 | — | 方框左上角 y 坐标 |
| `boxes[].w` | `number` | 是 | — | 方框宽度 |
| `boxes[].h` | `number` | 是 | — | 方框高度 |
| `boxes[].color` | `string` | 否 | `"#FF0000"` | 方框颜色 |
| `boxes[].label` | `string` | 否 | — | 方框标签文字 |
| `boxes[].line_width` | `number` | 否 | 自适应 | 线宽(像素) |
| `normalized` | `boolean` | 否 | `false` | 坐标是否为 0-1 归一化值 |
| `output_path` | `string` | 否 | — | 保存路径 |
#### `image_draw_text` — 添加文字标记
支持中文,中文字体通过 `VISION_MCP_FONT_PATH` 指定,或自动检测系统 CJK 字体。
| 参数 | 类型 | 必需 | 默认值 | 说明 |
|------|------|------|--------|------|
| `image` | `string` | 是 | — | 图片路径或 URL |
| `texts` | `object[]` | 是 | — | 文字列表 |
| `texts[].x` | `number` | 是 | — | 文字左上角 x 坐标 |
| `texts[].y` | `number` | 是 | — | 文字左上角 y 坐标 |
| `texts[].text` | `string` | 是 | — | 文字内容(支持中文) |
| `texts[].font_size` | `number` | 否 | 24 | 字号(像素) |
| `texts[].color` | `string` | 否 | `"#FF0000"` | 文字颜色 |
| `texts[].background_color` | `string` | 否 | — | 文字背景色 |
| `normalized` | `boolean` | 否 | `false` | 坐标是否为 0-1 归一化值 |
| `output_path` | `string` | 否 | — | 保存路径 |
## 使用示例
### 示例 1:检测图片中的目标并标注
```
Agent: 我要找到这张图片中所有人的位置并标注出来
工具调用流程:
1. vision_detect(images=["photo.jpg"], target="人")
→ { detections: [{ label: "人", bbox: {x_min:0.1, y_min:0.2, x_max:0.3, y_max:0.5}, confidence: 0.9 }] }
2. image_draw_box(
image="photo.jpg",
boxes=[{ x:0.1, y:0.2, w:0.2, h:0.3, color:"#FF0000", label:"人" }],
normalized=true,
output_path="annotated.png"
)
→ 返回标注后的图片
```
### 示例 2:提取 PPT 中的文字
```
Agent: 提取这页 PPT 的所有文字内容
工具调用:
1. vision_ocr(images=["slide.png"])
→ { text_blocks: [...], full_text: "标题\n正文内容..." }
```
### 示例 3:检查 Web 页面是否符合设计要求
```
Agent: 检查这个页面截图的导航栏是否在顶部,按钮颜色是否为蓝色
工具调用:
1. vision_qa(images=["screenshot.png"], question="导航栏是否在页面顶部?按钮颜色是什么?")
→ "导航栏在页面顶部。按钮颜色为蓝色。"
```
### 示例 4:拼接多张截图后整体解读
```
Agent: 把这三张页面截图拼在一起,然后整体描述
工具调用:
1. image_concat(images=["p1.png","p2.png","p3.png"], layout="vertical")
→ 返回拼接后的图片
2. vision_describe(images=[拼接结果], detail_level="detailed")
→ 整体描述
```
## 项目结构
```
vision-mcp/
├── package.json
├── tsconfig.json
├── src/
│ ├── index.ts # 入口:server 初始化 + stdio 传输
│ ├── constants.ts # 环境变量、默认值、忠实性提示词
│ ├── types.ts # 共享类型定义
│ ├── schemas.ts # Zod 输入/输出 schema
│ ├── services/
│ │ ├── image-loader.ts # 路径/URL → buffer + metadata
│ │ ├── vlm-client.ts # OpenAI 兼容 VLM API 客户端
│ │ ├── image-processor.ts # sharp: resize/crop/rotate/flip/concat
│ │ └── image-annotator.ts # @napi-rs/canvas: draw_box/draw_text
│ └── tools/
│ ├── vlm.ts # 4 个 VLM 工具
│ └── image.ts # 8 个图像处理工具
├── eval/
│ ├── setup.mjs # 测试图片生成
│ ├── evaluation.xml # 评估问题
│ ├── test-all.mjs # 完整测试脚本
│ └── images/ # 测试图片
└── dist/ # 编译输出
```
## 技术栈
| 组件 | 技术 | 用途 |
|------|------|------|
| MCP SDK | `@modelcontextprotocol/server` v2 | MCP 协议实现 |
| Schema 校验 | Zod v4 | 输入/输出验证 |
| 图像变换 | sharp | resize/crop/rotate/flip/concat/metadata |
| 图像标注 | @napi-rs/canvas | draw_box/draw_text(中文支持) |
| VLM 调用 | 原生 fetch | OpenAI 兼容 API,零额外依赖 |
| 传输方式 | stdio | 本地集成,单用户场景 |
## 设计要点
### 忠实性保障
- 所有 VLM prompt 内置忠实性指令:"只描述你在图片中能直接看到的内容,不要推测、脑补或添加图片中不存在的信息"
- `temperature: 0` 确保确定性输出
- 检测工具要求模型在不确定时返回空结果
### 坐标归一化双重保障
1. **系统提示词**:`DETECTION_SYSTEM_PROMPT` 强制要求 0-1 归一化坐标,说明归一化公式
2. **后处理**:`normalizeBbox` 自动检测像素坐标(任一值 > 1.0),除以图片尺寸归一化
### 图像输出
- 始终返回 base64 PNG 图片内容(MCP `image` content type),LLM 可直接看到处理结果
- 可选 `output_path` 参数保存到文件,适合大图和后续使用
- `structuredContent` 包含结果图片的宽高、格式、大小
### 中文字体支持
`image_draw_text` 的字体查找优先级:
1. `VISION_MCP_FONT_PATH` 指定的字体文件
2. 系统 CJK 字体(Microsoft YaHei / SimHei / PingFang SC / Noto Sans CJK SC 等)
3. `sans-serif` 回退(可能无法渲染中文)
## 开发
```bash
# 开发模式(热重载)
npm run dev
# 构建
npm run build
# 运行测试
node eval/test-all.mjs
# 生成测试图片
node eval/setup.mjs
```
## 许可证
MIT
TDQS
Scored across 12 tools
The vision_ tools are mostly distinct: qa asks questions, describe gives faithful descriptions, detect returns bounding boxes, and ocr extracts text. The image_ tools are clearly separated by operation. There is slight overlap between vision_qa and vision_describe, but the parameter and return descriptions make their use cases reasonably distinguishable.
Tool names follow a clear two-prefix convention: vision_* for understanding tasks and image_* for manipulation/annotation tasks. Minor deviations exist—vision_qa and vision_ocr are noun-like rather than verb-like, and image_get_metadata uses get while other image tools do not—but overall the naming is readable and predictable.
Twelve tools is a well-sized surface for a vision MCP server: four vision analysis tools and eight image processing/annotation tools. Each tool covers a distinct operation without bloat.
The tool set covers the core vision workflow well: understand, describe, detect, OCR, transform, and annotate images. Minor gaps like explicit format conversion or color/quality adjustments exist, but they are not critical for typical visual QA and image inspection use cases.