VisionToolMCP
# VisionToolMCP
VisionToolMCP 是一个 MCP 服务器,为纯文本 Agent 提供视觉能力桥梁。它接受图像文件、base64 图像数据或图像 URL,将它们发送到多模态模型,并通过 MCP 返回文本内容和 `structuredContent` 结构化结果。
## 功能特性
- 📤 **图像上传** - 通过 base64 上传图像,获得 `imageId` 可重复使用,30 分钟自动清理
- 🔍 **图像描述** - 描述图像内容,支持可选的聚焦/指令引导
- 📝 **文字识别 (OCR)** - 从图像中提取可见文本
- ❓ **图像问答** - 回答关于单张图像的特定问题
- 🆚 **图像对比** - 比较两张图像并总结相关差异
## 环境要求
- Node.js 20+
- 设置以下任一 API 密钥(环境变量):`VISIONTOOL_API_KEY`、`ANTHROPIC_API_KEY`、`OPENAI_API_KEY` 或 `GEMINI_API_KEY`
## 安装
```bash
npm install
npm run build
```
## 运行
```bash
VISIONTOOL_API_FORMAT=gemini VISIONTOOL_API_KEY=你的密钥 npm run dev
```
## MCP 客户端配置
**所有配置统一放在项目根目录的 `.env` 文件中**(参考 [`.env.example`](.env.example) 复制为 `.env` 并编辑)。服务器启动时从项目根加载 `.env` 且以它为唯一来源(`override: true`),MCP 客户端的 `env` 块**不需要也不起作用**——避免客户端缓存旧配置导致行为漂移。客户端配置只需指向编译后的服务器:
```json
{
"mcpServers": {
"visiontool": {
"command": "node",
"args": ["X:/MCP/VisionToolMCP/dist/index.js"]
}
}
}
```
## 配置选项
支持以下环境变量配置:
| 环境变量 | 说明 | 默认值 |
|---------|------|--------|
| `VISIONTOOL_API_FORMAT` | API 格式:`anthropic`、`openai` 或 `gemini` | `anthropic` |
| `VISIONTOOL_API_KEY` | 统一 API 密钥,也可使用提供商特定的密钥(如 `ANTHROPIC_API_KEY`、`OPENAI_API_KEY`、`GEMINI_API_KEY`) | - |
| `VISIONTOOL_MODEL` | 使用的模型 | `claude-opus-4-8` (Anthropic) / `gpt-5.6-terra` (OpenAI) / `gemini-2.5-flash` (Gemini) |
| `VISIONTOOL_BASE_URL` | API 基础 URL | - |
| `VISIONTOOL_TIMEOUT_MS` | 请求超时(毫秒) | `60000` |
| `VISIONTOOL_MAX_IMAGE_BYTES` | 本地/base64 图像最大大小 | `5242880` |
| `VISIONTOOL_RETRIES` | 429/5xx 等临时 API 故障的重试次数 | `2` |
| `VISIONTOOL_RETRY_BASE_MS` | 指数退避重试的基础延迟 | `250` |
| `VISIONTOOL_ALLOWED_IMAGE_ROOTS` | 限制本地图像路径必须位于这些根目录下;多个目录用系统路径分隔符分隔 | 不限制 |
| `VISIONTOOL_DISABLE_URL_INPUTS` | 设为 `1`/`true`/`yes`/`on` 时禁用图像 URL 输入 | `false` |
| `VISIONTOOL_ALLOWED_URL_HOSTS` | 限制图像 URL host,逗号分隔;支持 `*.example.com` | 不限制 |
| `VISIONTOOL_ALLOW_PRIVATE_URLS` | 允许 localhost/private IP 图像 URL 被发送给上游视觉模型 | `false` |
| `VISIONTOOL_PROXY_URL` | 网络错误后重试使用的代理 URL | `HTTP_PROXY` / `HTTPS_PROXY`(默认**不启用**代理 fallback) |
| `VISIONTOOL_DISABLE_PROXY_FALLBACK` | 设为 `1`/`true`/`yes`/`on` 时关闭代理 fallback | `false` |
| `VISIONTOOL_BLOCK_CALLER_SUBSTRINGS` | 调用者模型名黑名单,用逗号分隔;设置后 `_caller_model` 变为必填,名字**包含**任一关键字的模型被拒绝(子串匹配,如 `gpt` 可挡 `gpt-4o`、`opencode/gpt-5.5`、`azure-gpt-5`) | 空(默认放行所有调用者) |
| `VISIONTOOL_UPLOAD_TTL_MS` | 上传图像的自动过期时间(毫秒) | `1800000` (30 分钟) |
| `VISIONTOOL_ENABLE_OPENCODE` | 设为 `1`/`true`/`yes`/`on` 时启用 opencode 专属的 `opencode_pasted_image` 工具(自动检测 `~/.local/share/opencode/opencode.db`) | `false` |
| `VISIONTOOL_OPENCODE_DB` | 显式指定 opencode 数据库路径;设置后同样启用 `opencode_pasted_image` 工具 | - |
## 调用者护栏
此 MCP 服务器**默认对任何调用者开放**,不要求 `_caller_model`。理由:是否需要视觉能力是**上下文判断**而非身份判断--同一个多模态模型,能直接看到图片时不该调用,拿到 `[Unsupported Image]` 时就该调用。靠模型名白名单拦截会误伤正当用户(比如 Claude 因 harness 传输问题看不到图、却需要求助),且白名单本身可被调用方伪造,并非硬保证。
护栏放在**工具描述**里:每个视觉工具的描述都写明"仅当你无法直接看到图片时调用,能直接看到图的多模态模型请勿调用",由调用方自判。
如果需要**硬保证**某个模型族永不路由到本服务器(例如防止某个多模态模型误调用浪费额度),可设置可选黑名单:
```bash
# 永不允许 claude / gpt 系列模型调用
VISIONTOOL_BLOCK_CALLER_SUBSTRINGS=claude,gpt
```
设置黑名单后:
- `_caller_model` 变为**必填**(否则拒绝,因为无法判断是否在黑名单内)
- 匹配方式为**子串包含**:只要模型名(含 `provider/` 前缀部分)中出现任一关键字即被挡。例如 `gpt` 可挡 `gpt-4o`、`opencode/gpt-5.5`、`azure-gpt-5`
完整配置示例请参考 [`.env.example`](.env.example) 文件。
## opencode 专属:opencode_pasted_image
这是一个**仅 opencode 适用**的工具,默认不注册。当 MCP 服务运行在 opencode 会话内时,设置 `VISIONTOOL_ENABLE_OPENCODE=1`(或 `VISIONTOOL_OPENCODE_DB=<opencode.db 路径>`)即可启用。
**为什么需要它**:在 opencode 中粘贴的图片以 base64 data URL 内联存储在 `opencode.db` 的 `part` 表里,**不落盘**。纯文本调用模型(如 GLM/DeepSeek)接不到图片附件,会报 `Cannot read "image.png" (this model does not support image input)`,图片被丢弃。该工具直接从数据库里把"当前会话最新一张粘贴图"取出来,解码落盘并注册为上传,返回 `imageId`/`path` 供其它视觉工具使用。
**会话识别**:用"最新一条 `message` 所属的 session"作为当前会话(比 `session.time_updated` 更可靠,后者会被 UI 选中事件刷新)。只在当前会话内取最新图片,**不会跨会话兜底**——当前会话没有粘贴图时会直接报错,避免拿错别的对话的图。
**用法**:
```json
{
"name": "opencode_pasted_image",
"arguments": {}
}
```
响应:
```json
{
"tool": "opencode_pasted_image",
"imageId": "6b49d277-c27d-434c-8e52-8601c8f5a1fb",
"path": "C:\\...\\visiontool-mcp-uploads\\image.png",
"bytes": 356351,
"mediaType": "image/png",
"filename": "image.png",
"sessionId": "ses_xxx",
"timeCreated": 1785249912775,
"expiresAt": "2026-07-28T15:31:08.235Z"
}
```
然后用 `imageId` 调用其它工具:
```json
{
"name": "describe_image",
"arguments": {
"image": { "imageId": "6b49d277-c27d-434c-8e52-8601c8f5a1fb" }
}
}
```
依赖 Node 22.5+ 内置的 `node:sqlite`(运行时动态导入,不可用时该工具会清晰报错,不影响其它工具)。其它 agent 平台(无 opencode 数据库)请勿启用。
## 工具调用示例
所有工具默认无需 `_caller_model`(仅在设置了 `VISIONTOOL_BLOCK_CALLER_SUBSTRINGS` 黑名单时才必填);`reasoningEffort` 可选但**默认 `medium`**(可填 `low` / `medium` / `high` / `xhigh`)。
### 图像上传(推荐)
对于对话中的截图或 Agent 自己生成的图像,先上传获得 `imageId` 可多次使用,30 分钟后自动清理:
```json
{
"name": "upload_image",
"arguments": {
"base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
"mediaType": "image/png",
"filename": "screenshot"
}
}
```
响应:
```json
{
"tool": "upload_image",
"imageId": "550e8400-e29b-41d4-a716-446655440000",
"path": "/tmp/visiontool-mcp-uploads/screenshot.png",
"bytes": 1234,
"expiresAt": "2024-06-25T03:30:00.000Z"
}
```
然后用 `imageId` 调用其他工具:
```json
{
"name": "describe_image",
"arguments": {
"image": {
"imageId": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
```
### 其他输入方式
也支持 `path`(本地文件)、`base64`(直接传数据)或 `url`(公开 URL):
```json
{
"name": "describe_image",
"arguments": {
"image": {
"path": "X:/screenshots/current.png"
},
"detail": "medium",
"maxTokens": 1024,
"model": "gpt-5.6-terra",
"reasoningEffort": "medium"
}
}
```
所有视觉工具(`describe_image` / `ocr_image` / `answer_about_image` / `compare_images`)都支持 `reasoningEffort` 参数(`low` / `medium` / `high` / `xhigh`,**默认 `medium`**),透传给上游模型的思考强度:
- **OpenAI 格式**:映射为 `reasoning_effort` 字段。
- **Anthropic 格式**:映射为 `thinking.type=enabled`,预算按强度取 1024 / 4096 / 8192 / 16384 token(预算必须小于 `maxTokens` 才会发送)。
- **Gemini 格式**:映射为 `generationConfig.reasoningConfig.effort`。
- 不传时该字段被省略,使用模型自身的默认推理强度。
注意:各视觉工具默认 `maxTokens` 为 **2048**(`ocr_image` 2048、`compare_images` 1536),保证 Anthropic 格式下默认的 `reasoningEffort: medium` 预算(1536 token)始终有效;如果手动把 `maxTokens` 调低到 1365 以下,Anthropic 的 thinking 预算会因不满足 `>= 1024` 而被跳过。
工具响应会同时包含可读文本和结构化对象。结构化对象字段为:
```json
{
"tool": "describe_image",
"model": "gemini-2.5-flash",
"apiFormat": "gemini",
"text": "model response text",
"images": [
{
"source": "path",
"mediaType": "image/png",
"path": "X:/screenshots/current.png",
"bytes": 12345
}
]
}
```
## 图像输入安全边界
- `imageId` - 通过 `upload_image` 返回的 ID,30 分钟自动清理(推荐,最安全)
- `path` - 绝对或相对本地图像路径
- `base64` - 原始 base64 图像数据
- `url` - 可公开访问的图像 URL
支持的 MIME 类型:PNG、JPEG、WebP 和 GIF。
注意:`path` 会读取 MCP 服务器进程可访问的本地文件;`url` 会把 URL 交给上游视觉模型提供商读取。生产环境建议设置 `VISIONTOOL_ALLOWED_IMAGE_ROOTS`、`VISIONTOOL_ALLOWED_URL_HOSTS`,或用 `VISIONTOOL_DISABLE_URL_INPUTS=1` 禁用 URL 输入。默认会拒绝 localhost/private IP URL;确有需要时才设置 `VISIONTOOL_ALLOW_PRIVATE_URLS=1`。
## 网络与代理
遇到可重试的网络错误时,服务器会先直连,再通过代理 fallback 重试。代理**默认不启用**(不猜测任何本地代理端口,避免把带 API key 的请求发给无关的本机服务);仅在显式设置了 `VISIONTOOL_PROXY_URL`(或环境中的 `HTTPS_PROXY` / `HTTP_PROXY`)时才启用,选择顺序为 `VISIONTOOL_PROXY_URL`、`HTTPS_PROXY`、`HTTP_PROXY`。设置 `VISIONTOOL_DISABLE_PROXY_FALLBACK=1` 可强制关闭。
## 开发
```bash
npm test
npm run build
```
## Agent 使用说明
此服务器与截图/捕获 MCP 配合使用。先用另一个工具截取屏幕截图,将返回的文件路径传递给 `describe_image` 或 `answer_about_image`,然后使用结构化的文本响应来决定下一步操作。
TDQS
Scored across 4 tools
Each tool targets a distinct capability: answering specific questions, comparing two images, describing content, and extracting text. There is no overlap in their purposes.
All tools use snake_case with verb-first pattern (answer, compare, describe, ocr). However, 'answer_about_image' uses a preposition while others directly combine verb and noun, a minor inconsistency.
Four tools is appropriate for a focused vision server providing core image understanding capabilities. Not too few or too many.
Covers key image interpretation needs: description, comparison, OCR, and question answering. Missing potential features like object detection or image generation, but the set is reasonably complete for its stated purpose of supporting text-only agents.