Skip to main content
Glama
README.md
# image-viewer-mcp

一个用 Node.js 编写的 MCP 服务器。它接收图片的本地路径或 URL,把图片交给视觉语言模型(VLM)识别内容,返回三段结构化的描述,让只看得见文字的 LLM 也能完整理解图片。

## 背景

纯文本的 LLM 看不到图片。如果需要让它在对话中理解一张截图、一张图表或一张照片,常见的做法是借助外部视觉模型代为描述。本项目以 MCP 工具的形式提供这种能力,任何支持 MCP 的客户端(如 Claude Code、Claude Desktop)都可以直接调用。

## 功能

- 输入:本机图片的绝对路径、http(s) 图片 URL,或 data: 内联图片 URL
- 可选输入:图片背景信息(context),帮助模型更精准地识别
- 输出:三个字段,按工具调用的结构化结果返回

| 字段 | 含义 |
|---|---|
| `brief` | 简要描述图片 |
| `description` | 详细描述图片 |
| `summary` | 总结图片的意图、潜在含义、代表意义 |

- 返回格式可切换:text(可读文本)、json(JSON 字符串)、raw(VLM 原始输出),默认取配置 `output.format`,调用时可用 `format` 参数覆盖
- 识别缓存:按图片内容 hash 复用识别结果,同图同配置直接返回缓存,节省 API 调用;默认启用,调用时可用 `use_cache` 参数控制
- 支持格式:PNG / JPG / JPEG / GIF / WEBP / **AVIF** / **SVG**,单张不超过 20MB
  - SVG 在服务端用 `@resvg/resvg-js` 自动转成 PNG 后再送模型,调用方无感(本地 SVG 文件或 SVG URL 均可);尺寸过小(<14px)的 SVG 会自动放大到模型可读
  - AVIF 在服务端用 sharp 自动转码为 PNG 再送模型,不依赖模型侧对 AVIF 的支持
  - 过小的位图(如 icon)按整数倍自动放大到模型可读尺寸,避免被模型拒绝
- 后端默认使用 OpenAI 的 `gpt-4o` 视觉模型,可通过配置更换模型或改用任何兼容 OpenAI 协议的服务

## 工作原理

```mermaid
flowchart LR
    A[客户端调用 analyze_image] --> B{图片来源}
    B -->|本地路径| C[读取文件,按扩展名识别 MIME]
    B -->|http URL| D[下载图片,按响应头识别 MIME]
    B -->|data URL| E[解析内联 base64]
    C --> F[base64 编码]
    D --> F
    E --> F
    F --> G[组装多模态消息]
    G --> H[调用 VLM 接口]
    H --> I[解析三段 XML 标签]
    I --> J[返回 brief / description / summary]
```

## 安装

要求 Node.js 18 或更高版本。

```bash
npm install
```

## 配置

配置有两种方式:`config.yaml` 配置文件和环境变量。环境变量的优先级高于配置文件,便于在 MCP 客户端(如 Claude Code)中直接注入 Key。

优先级从高到低:环境变量 > `config.yaml` > 内置默认值。

### 配置文件

复制 `config.example.yaml` 为 `config.yaml`,按需修改。`config.yaml` 已被 `.gitignore` 忽略,不会入库,可安全存放 API Key。

**查找位置**:默认在 `src/index.js` 所在目录的上一级(即项目根目录)查找 `config.yaml`,与启动服务器时的工作目录无关。也可以把配置文件放到任意位置,用 `--config <路径>` 参数或 `CONFIG_PATH` 环境变量指定;这两种方式给出的相对路径按当前工作目录解析。

除 `api_key` 外所有字段均可选,未写的字段使用内置默认值。完整字段如下:

| 字段 | 默认值 | 说明 |
|---|---|---|
| `api_key` | 无(必填) | OpenAI API Key |
| `base_url` | 官方地址 | 自定义 API 地址,兼容 OpenAI 协议的中转服务也可用 |
| `extra_headers` | 无 | 附加 HTTP 请求头,部分中转服务需要自定义鉴权头 |
| `model` | `gpt-4o` | 视觉模型名 |
| `max_tokens` | `2048` | 单次回答的最大 token 数 |
| `temperature` | `0.2` | 采样温度,越低越稳定,识别类任务建议 0 ~ 0.5 |
| `top_p` | 不发送 | 核采样概率,不写则按模型默认 |
| `timeout_seconds` | `120` | 单次请求超时(秒) |
| `max_retries` | `2` | 网络异常时自动重试次数 |
| `max_image_size_mb` | `20` | 图片大小上限(MB) |
| `download_timeout_seconds` | `30` | 网络图片下载超时(秒) |
| `svg.render_size` | `512` | SVG 未声明尺寸时的兜底渲染像素;密集元素建议调大 |
| `svg.min_pixel` | `14` | 渲染尺寸下限:SVG 低于则放大到 render_size;位图低于则整数倍放大 |
| `svg.auto_size` | `true` | 自动推断合适渲染尺寸(按最小字号 + 元素密度),声明尺寸也参与评估 |
| `svg.min_text_px` | `14` | 自动推断时最小文字渲染高度目标(像素) |
| `svg.density_px` | `20` | 自动推断时每个元素的平均占地(像素),越大越清晰 |
| `svg.max_render_size` | `4096` | 自动推断的尺寸上限 |
| `prompts` | 见下方 | 提示词配置,全部可自定义 |
| `output.format` | `text` | 返回格式:`text` 可读文本、`json` JSON 字符串、`raw` VLM 原始输出,调用时可覆盖 |
| `cache.enabled` | `true` | 是否默认启用识别缓存,调用时可覆盖 |
| `cache.dir` | `.cache` | 缓存目录,相对项目根目录 |
| `cache.key_components` | 三个均开 | 缓存键组成,image / config / context 各可独立开关 |

### 提示词配置

`prompts` 段控制发给 VLM 的全部提示词。三段输出的标签(tag)、标题(heading)、可读名称(label)均可自定义;用户提示词由配置自动生成,修改 tag 后提示词与解析器自动同步,不需要改两处。

```yaml
prompts:
  system: |                      # 系统提示:约束角色、输出语言与回答纪律
    你是一个专业的图片内容识别助手。只依据图片和给定的背景信息进行描述,
    不虚构图片中不存在的细节。必须使用中文回答。
  instruction: |                 # 指令:紧跟图片发送,说明任务目标
    详细描述图片内容,目标是让仅能看到文字的LLM能够完全理解图片内容。
    回答格式:
  context_label: "图片背景信息(仅作参考):"   # 附加 context 参数时的引导语
  require_all_sections: true     # true 时任何一段缺失都按格式违规;false 时缺失段落留空
  sections:                      # 三段输出配置;key 固定,可只写想改的段,其余继承默认值
    - key: brief                 # 与工具输出字段对应
      tag: image_brief           # 模型回答中的包裹标签,解析器按此提取
      heading: 简要描述图片       # 提示词中该段的占位说明
      label: 简要                # 可读文本中该段的名称
    - key: description
      tag: image_description
      heading: 详细描述图片
      label: 详细
    - key: summary
      tag: image_summary
      heading: 总结图片意图/潜在含义/代表意义等
      label: 总结
```

### 环境变量

| 环境变量 | 说明 |
|---|---|
| `OPENAI_API_KEY` | OpenAI API Key,设置后覆盖配置文件中的 `api_key` |
| `OPENAI_BASE_URL` | 自定义 API 地址,覆盖配置文件中的 `base_url` |
| `VLM_MODEL` | 视觉模型名,覆盖配置文件中的 `model` |
| `VLM_MAX_TOKENS` | 单次回答的最大 token 数,覆盖配置文件中的 `max_tokens` |

缺少 API Key 时服务器会拒绝启动并给出明确提示。

## 在 Claude Code 中使用

在 `~/.claude.json` 或项目的 `.mcp.json` 中添加以下配置:

```json
{
  "mcpServers": {
    "image-viewer": {
      "command": "node",
      "args": ["/绝对路径/image-viewer-mcp/src/index.js"],
      "env": {
        "OPENAI_API_KEY": "sk-你的key"
      }
    }
  }
}
```

配置完成后,直接让 Claude 描述一张图片即可,例如:

> 帮我看看 /home/user/screenshots/bug.png 里有什么,这是报错截图

## 手动测试

```bash
# 运行单元测试(提示词生成、解析、配置校验)
npm test

# 列出工具
node test/client.js --list

# 识别本地图片(可附加背景信息)
node test/client.js /path/to/image.png "这是监控截图"

# 识别网络图片
node test/client.js "https://example.com/image.jpg"

# 指定返回格式
node test/client.js /path/to/image.png --format json

# 强制重新识别(不使用缓存)
node test/client.js /path/to/image.png --no-cache

# 使用 config.yaml 配置后直接运行服务器(stdio 模式)
npm start

# 指定其他位置的配置文件
node src/index.js --config /path/to/config.yaml
```

## 输出示例

`analyze_image` 调用成功时返回:

```json
{
  "content": [
    { "type": "text", "text": "【简要】...\n\n【详细】...\n\n【总结】..." }
  ],
  "structuredContent": {
    "brief": "简要描述",
    "description": "详细描述",
    "summary": "总结"
  },
  "_meta": { "cache": "hit" }
}
```

- `content` 通道按 `format` 显示:`text`(默认)为可读文本,`json` 为 JSON 字符串,`raw` 为 VLM 原始输出
- `structuredContent` 始终是结构化的三段字段,供程序化消费
- `_meta.cache` 表示本次结果来源:`hit` 命中缓存、`miss` 新识别并写入缓存、`disabled` 未启用缓存

失败时(文件不存在、格式不支持、网络错误、API 报错等)返回 `isError: true` 和中文错误说明,调用方 LLM 可以直接看到原因。

## 缓存机制

缓存键由三部分构成,各部分在 `cache.key_components` 中独立开关。参与计算的组件任一变化都会得到不同的缓存,避免返回过期结果:

- `image`:图片内容 hash(同一张图无论来自路径还是 URL 都相同)
- `config`:模型与提示词配置 hash(改配置后缓存自动失效)
- `context`:背景信息 hash(不同背景要求不同的识别结果)

例如背景信息可能频繁变化、又希望同一张图复用缓存时,可以关闭 `context` 组件:

```yaml
cache:
  key_components:
    context: false
```

关闭后,同一张图无论背景怎么变都命中同一缓存。至少需要启用一个组件,全部关闭会在启动时报错。

缓存默认写入项目根目录的 `.cache/`(已被 .gitignore 忽略),每张图一条 JSON 文件。每条缓存保存完整的模型与提示词参数(model / max_tokens / temperature / top_p / prompts 等)以及命中所用的键组成,便于排查缓存为何命中或失效。缓存读失败或写失败都不影响主流程。

## 限制

- 图片最大 20MB,与 OpenAI 视觉接口的上限一致
- 模型的识别质量取决于所配置的 VLM,更换更强或更弱的模型会影响输出精度
- 图片数据以 base64 内联形式发送给配置的模型服务商,请勿在需要保密的场景上传敏感图片

Maintenance

ActivityMaintained
ResponsivenessNo issues