openPic-mcp
openPic-mcp 是一个基于 MCP (Model Context Protocol) 协议的图像能力服务器,为 AI 编程助手提供图片理解、图片比较、图片生成和图片编辑能力。它支持任何 OpenAI-Compatible 的图像 API 服务。
⚠️ 当前版本说明:当前主路径是 stdio 传输,可使用本地编译后的可执行文件,也可通过go run github.com/AoManoh/openPic-mcp/cmd/vision-mcp@master在线拉取源码并在本机运行。uvx/npx包装入口和远端 Streamable HTTP 服务仍属于后续规划。
功能特性
- MCP 协议兼容:实现 MCP 协议规范,支持 stdio 传输
- OpenAI-Compatible:支持任何兼容 OpenAI 图像能力接口的服务
- 多种图片输入:支持 Base64 编码、Data URI、HTTP/HTTPS URL、本地文件路径
- 多种图片格式:支持 JPEG、PNG、WebP、GIF、BMP、TIFF、ICO、HEIC、AVIF、SVG 格式
- 图像比较:支持 2-4 张图片的智能比较分析
- 图片生成与编辑:支持通过 OpenAI-Compatible
/images/generations和/images/edits路由生成或编辑图片
快速开始
前置条件
- Go 1.23 或更高版本(本地构建)
- OpenAI API 密钥或兼容服务的访问凭证
本地运行
- 克隆项目
git clone https://github.com/AoManoh/openPic-mcp.git
cd openPic-mcp- 配置环境变量
cp .env.example .env
# 编辑 .env 文件,填写实际配置- 构建并运行
go build -o openPic-mcp ./cmd/vision-mcp
./openPic-mcp在线拉取运行(Go)
如果本机已安装 Go 1.23 或更高版本,可以不手动克隆仓库,直接在 MCP 客户端中使用 go run 拉取并运行:
{
"mcpServers": {
"openPic-mcp": {
"command": "go",
"args": [
"run",
"github.com/AoManoh/openPic-mcp/cmd/vision-mcp@master"
],
"env": {
"OPENPIC_API_BASE_URL": "https://your-server.com/v1",
"OPENPIC_API_KEY": "your-api-key",
"OPENPIC_VISION_MODEL": "your-vision-model-name",
"OPENPIC_IMAGE_MODEL": "your-image-model-name",
"OPENPIC_TIMEOUT": "5m"
}
}
}
}国内网络环境下,首次运行需要下载 Go 模块和依赖,可临时在 MCP 配置的 env 中增加 Go 代理加速:
{
"env": {
"GOPROXY": "https://goproxy.cn,direct",
"GOSUMDB": "sum.golang.google.cn"
}
}GOPROXY 能加速公开 Go 模块下载;GOSUMDB 使用 Go checksum database 的国内可访问镜像。该方式仍然需要本机安装 Go,并且首次启动会在本机下载依赖和编译,速度取决于网络和机器性能。
Docker 运行
⚠️ 注意:当前版本仅支持 stdio 传输,Docker 部署暂时无法直接用于 MCP 服务配置。Docker 相关文件是为后续支持 HTTP 传输(Streamable HTTP)预留的,届时将支持端口暴露和远程调用。当前阶段请使用本地运行方式。
Docker 命令参考(开发测试用)
- 配置环境变量
cp .env.example .env
# 编辑 .env 文件,填写实际配置- 使用 Docker Compose 启动
docker-compose up -d --build- 查看日志
docker-compose logs -f- 停止服务
docker-compose down直接使用 Docker
docker build -t openpic-mcp:latest .
docker run -it --rm \
-e OPENPIC_API_BASE_URL=https://api.openai.com/v1 \
-e OPENPIC_API_KEY=your-api-key \
-e OPENPIC_VISION_MODEL=gpt-5.5 \
-e OPENPIC_IMAGE_MODEL=gpt-image-2 \
openpic-mcp:latest配置说明
所有配置通过环境变量设置。可以创建 .env 文件或直接设置环境变量。新配置优先使用 OPENPIC_*,并兼容旧的 VISION_*。
| 环境变量 | 必填 | 默认值 | 说明 |
|---|---|---|---|
OPENPIC_API_BASE_URL | 是 | - | OpenAI-Compatible API 基础 URL,兼容 VISION_API_BASE_URL |
OPENPIC_API_KEY | 是 | - | API 密钥,兼容 VISION_API_KEY |
OPENPIC_VISION_MODEL | 是 | - | 视觉理解模型,兼容 VISION_MODEL |
OPENPIC_IMAGE_MODEL | 使用 generate_image 或 edit_image 时必填 | - | 图片生成或编辑模型 |
OPENPIC_TIMEOUT | 否 | 5m | API 请求超时时间,兼容 VISION_TIMEOUT |
OPENPIC_LOG_LEVEL | 否 | info | 日志级别,兼容 VISION_LOG_LEVEL |
OPENPIC_OUTPUT_DIR | 否 | 空(使用 os.TempDir()/openpic-mcp/) | generate_image / edit_image 默认落盘目录,必须为绝对路径;可被工具入参 output_dir 覆盖 |
OPENPIC_FILENAME_PREFIX | 否 | 空(使用工具上下文 generate / edit) | 默认文件名前缀,仅允许 [A-Za-z0-9._-]、最长 32 字符、不能以 . 开头;可被工具入参 filename_prefix 覆盖 |
OPENPIC_MAX_INLINE_PAYLOAD_BYTES | 否 | 1048576(1 MiB) | 内联 base64 payload 字节上限。b64_json 模式下超阈直接拒绝;file_path 模式下追加警告。设置 0 / 负值会回退到默认 |
OPENPIC_OVERWRITE | 否 | false | 落盘文件命名冲突时的策略:false 追加 -2/-3 等后缀,true 覆盖同名文件;可被工具入参 overwrite 覆盖 |
OPENPIC_MAX_CONCURRENT_REQUESTS | 否 | 16 | 同时执行的 tools/call 上限;硬上限 100;0/负值/解析失败回退默认;超过上限自动 clamp |
OPENPIC_REQUEST_QUEUE_SIZE | 否 | 64 | tools/call 等待 worker 的有界队列长度;硬上限 10000;同上的 clamp/回退规则。队列满时 recv loop 同步回退处理(绝不丢请求) |
OPENPIC_REQUEST_TIMEOUT | 否 | 0s(不限) | 单个 tools/call 的最大执行时间。0s 表示不超时;图片生成可能需要 1-4 分钟,缺省值正是为了不误杀 |
OPENPIC_SHUTDOWN_TIMEOUT | 否 | 30s | 收到 SIGINT / SIGTERM 后等待 in-flight tools/call 完成的预算;超时则 engineCancel 强制收尾。必须 > 0 |
OPENPIC_LOG_FORMAT | 否 | text | text 或 json。所有日志一律写 stderr,stdout 仅承载 MCP JSON-RPC 帧 |
OPENPIC_TASK_STORE_ENABLED | 否 | true | 异步任务工具集总开关。false 时 submit_image_task / get_task_result / list_tasks / cancel_task 不注册,且不构造 store/dispatcher |
OPENPIC_TASK_DISK_PERSIST | 否 | true | 是否把任务 manifest 落盘到 /tasks/。true → DiskStore(启动期 fail-fast 校验目录可写);false → MemoryStore,重启即丢 |
OPENPIC_TASK_MAX_QUEUED | 否 | 256 | store 中 queued 状态任务上限;硬上限 10000;满则 submit 返回 ErrQueueFull |
OPENPIC_TASK_MAX_RETAINED | 否 | 1024 | 终态任务保留窗口;硬上限 100000;超过时按 finished_at 升序淘汰最旧 |
OPENPIC_TASK_TTL | 否 | 24h | 终态保留时间(Go duration)。GC 按此清理过期任务;非法/零/负值在 Load 直接报错 |
注意:OPENPIC_TIMEOUT/VISION_TIMEOUT必须使用 Go 的 duration 格式,例如:30s(30秒)、2m(2分钟)、5m(5分钟)。纯数字如120会导致解析错误。部分图片生成或编辑模型单次推理可能需要 1-4 分钟,不建议将该值设置得过低。 服务器并发与生命周期相关的 5 项变量同样使用 Go duration(*_TIMEOUT)和正整数(*_REQUESTS、*_QUEUE_SIZE)格式。详见下方 服务器并发与生命周期 章节。
配置示例
OpenAI(截至 2026-04-29,以 OpenAI API 实际可用为准):
OPENPIC_API_BASE_URL=https://api.openai.com/v1
OPENPIC_API_KEY=your-openai-api-key
# 视觉模型可选:
# - gpt-5.5(2026-04-23 旗舰,推荐默认)、gpt-5.5-pro(高精度)
# - gpt-5.4 / gpt-5.4-pro(平衡)
# - gpt-5.4-mini / gpt-5.4-nano(轻量、高并发、低成本)
# - gpt-5 / gpt-5.2(旧版 snapshot 回退)
OPENPIC_VISION_MODEL=gpt-5.5
# 图像模型可选:gpt-image-2(2026-04-21 旗舰、原生 4K、思考模式)、gpt-image-1.5 / gpt-image-1(前代回退)
OPENPIC_IMAGE_MODEL=gpt-image-2Azure OpenAI(部署名称由订阅决定,下方仅示意;Azure 上的 GPT-5.5 / gpt-image-2 可能比 OpenAI 直连晚到货):
OPENPIC_API_BASE_URL=https://your-resource.openai.azure.com/openai/deployments/your-deployment
OPENPIC_API_KEY=your-azure-api-key
# 视觉模型用 Azure 上对应的部署名(通常映射到 gpt-5.5 / gpt-5.4-mini)
OPENPIC_VISION_MODEL=your-vision-deployment-name
# 图像模型用 Azure 上对应的部署名(通常映射到 gpt-image-2 / gpt-image-1.5)
OPENPIC_IMAGE_MODEL=your-image-deployment-name自托管服务:
OPENPIC_API_BASE_URL=https://your-server.com/v1
OPENPIC_API_KEY=your-api-key
OPENPIC_VISION_MODEL=your-vision-model-name
OPENPIC_IMAGE_MODEL=your-image-model-nameMCP 配置示例
当前支持两种 stdio 使用方式:
- 使用本地编译后的可执行文件路径。
- 使用
go run github.com/AoManoh/openPic-mcp/cmd/vision-mcp@master在线拉取并在本机运行。
uvx / npx 形式尚未提供包装入口,因此当前不能直接使用 uvx --from git+... openpic-mcp。如需完全免 Go 环境,需要后续提供预编译二进制下载器或独立包管理器入口。
Claude Desktop
在 Claude Desktop 配置文件中添加:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"openPic-mcp": {
"command": "/path/to/openPic-mcp",
"args": [],
"env": {
"OPENPIC_API_BASE_URL": "https://api.openai.com/v1",
"OPENPIC_API_KEY": "your-api-key",
"OPENPIC_VISION_MODEL": "gpt-5.5",
"OPENPIC_IMAGE_MODEL": "gpt-image-2",
"OPENPIC_TIMEOUT": "5m"
}
}
}
}Cursor
在 Cursor MCP 配置中添加:
macOS: ~/.cursor/mcp.json Windows: %USERPROFILE%\.cursor\mcp.json
{
"mcpServers": {
"openPic-mcp": {
"command": "D:\\path\\to\\openPic-mcp.exe",
"args": [],
"env": {
"OPENPIC_API_BASE_URL": "https://api.openai.com/v1",
"OPENPIC_API_KEY": "your-api-key",
"OPENPIC_VISION_MODEL": "gpt-5.5",
"OPENPIC_IMAGE_MODEL": "gpt-image-2",
"OPENPIC_TIMEOUT": "5m"
}
}
}
}重要:args字段必须显式指定(即使为空数组[]),否则 MCP 客户端可能无法正确启动服务。
服务器并发与生命周期(C4/C5 引入)
openPic-mcp 内置了一个用 Go 写的 MCP 服务器引擎(internal/server.Server),负责调度所有 JSON-RPC 消息、并发执行 tools/call、传递取消信号和优雅停机。它就在 cmd/vision-mcp/main.go 里被直接装配,用户不需要额外服务。
调度模型
┌──────────────────────────────────────────────────────────────────┐
│ MCP Client (stdio peer) │
└────────────────────────────┬─────────────────────────────────────┘
│ JSON-RPC frames
▼
┌────────────────────────────┐
│ internal/transport/Stdio │ stdout 仅协议帧
└─────────────┬──────────────┘
│
▼
┌────────────────────────────┐
│ recv loop (单 goroutine) │ 解析 envelope
└─────────┬──────────────────┘
│
┌─────────────────┼──────────────────────────────┐
│ │ │
▼ ▼ ▼
非 tools/call tools/call → workQueue notifications/cancelled
同步执行 (有界,cap=64) 命中 CancelRegistry,
(initialize/list) │ 直接中断 in-flight ctx
│
┌───────────┴───────────┐
▼ ▼
worker pool (16 workers) 队列满 → recv loop 同步回退执行
│
▼
protocol.MCPHandler.HandleMessage(ctx, raw)
│
▼
tools.*要点:
tools/call走 worker pool,同时最多并发OPENPIC_MAX_CONCURRENT_REQUESTS个;超过队列容量时由 recv loop 同步回退处理,永远不会丢请求。initialize/tools/list/notifications/*等轻量消息保持在 recv loop 同步派发,避免无谓的并发开销。notifications/cancelled通过protocol.CancellationRegistry直达对应 in-flighttools/call的 ctx,工具应在所有 HTTP / IO 调用上传播 ctx,让取消立即生效。
调优开关
| 维度 | 变量 | 默认值 | 调优建议 |
|---|---|---|---|
| 并发 worker 数 | OPENPIC_MAX_CONCURRENT_REQUESTS | 16 | 上游账户并发额度紧张时降低;本机算力富余、上游放得开时提高(最大 100) |
| 排队 buffer | OPENPIC_REQUEST_QUEUE_SIZE | 64 | 客户端瞬时高并发但希望尽量异步处理时调大(最大 10000);不希望累积时调小 |
| 单请求预算 | OPENPIC_REQUEST_TIMEOUT | 0s(不限) | 0s 是为了不误杀图片生成;如需为 tools/call 设硬超时,建议 ≥ 90s |
| 优雅停机预算 | OPENPIC_SHUTDOWN_TIMEOUT | 30s | 工具长耗时(图片生成)建议拉长到 60s–120s;日志/CI 场景缩到 5s–10s 也可 |
| 日志格式 | OPENPIC_LOG_FORMAT | text | 接 ELK/Loki 等日志栈选 json;本地终端调试选 text |
优雅停机
收到 SIGINT / SIGTERM,引擎按以下顺序收尾:
- recv loop 退出,停止接收新请求。
- 关闭 worker queue,workers 排空已入队的
tools/call。 inflight WaitGroup等待所有 in-flight 完成;超过OPENPIC_SHUTDOWN_TIMEOUT触发engineCancel,让 ctx-aware 工具立即返回错误。- in-flight 全部排空之后再关闭 stdio 连接,保证响应不会被截断。
可观测性
引擎统一通过 *slog.Logger 写 stderr,关键事件包括:
server.boot/server.started/server.stopped:生命周期边界。req.received/req.dispatched/req.completed/req.cancelled:每条请求一个完整链路。req.queue_full_fallback:队列满触发了同步回退;持续出现说明并发不足。req.panic:handler panic 已被引擎捕获(不会拖垮 worker),需关注。server.shutdown_timeout_exceeded/server.shutdown_force_abandon:停机预算被打穿,建议拉长OPENPIC_SHUTDOWN_TIMEOUT或排查长耗时工具。
重要:所有日志一律走 stderr。如果你看到日志混进 MCP 客户端的 JSON-RPC 通道,绝大概率是OPENPIC_LOG_FORMAT之外的代码路径误用了fmt.Println等写 stdout 的 API,请在仓库内 grep 修复后再上线。
异步任务模型(C8 引入)
为支持长耗时图片生成(90 秒 ~ 4 分钟),项目在不破坏现有同步工具的前提下,新增了一套异步 submit/get 工具集。AI 客户端可立即拿到 task_id 不阻塞会话,再按需轮询结果。
架构
client tools/call(submit_image_task)
│
▼
┌────────────────────────────────────┐
│ internal/taskstore │
│ ├─ MemoryStore (in-RAM canonical) │
│ └─ DiskStore (RAM + JSON文件) │── $OPENPIC_OUTPUT_DIR/tasks/.json
└────────────────────────────────────┘
│ store.Submit (queued)
▼
┌────────────────────────────────────┐
│ internal/tools.Dispatcher │
│ ├─ 独立 worker pool(与 sync MCP │
│ │ 的 worker pool 完全解耦) │
│ ├─ buffered queue(≥ MaxQueued) │
│ └─ 双登记 cancel(store + 自有 map)│
└────────────────────────────────────┘
│ Transition queued → running
│ 调底层 generate_image / edit_image handler closure
│ Transition running → completed / failed / cancelled / abandoned
▼
client tools/call(get_task_result with wait=30s) → 终态 + Result.FilePath关键决策:异步任务的 worker pool 不复用 server 引擎的同步 worker pool。同步tools/list、describe_image等毫秒级请求绝不能被 90 秒级图片生成挤占槽位。两个 pool 独立调度,但共享OPENPIC_MAX_CONCURRENT_REQUESTS作为容量基线。
状态机
┌──────► cancelled (cancel_task on queued)
│
queued ──► running ─┼──► completed
├──► failed
├──► cancelled (cancel_task on running)
└──► abandoned (shutdown / restart 残留)
queued ──► failed (dispatcher queue full / 内部错误)
queued ──► abandoned (shutdown 时尚未派发)非法迁移返回 ErrIllegalTransition,状态机原子化于 sync.RWMutex 写锁下执行。
4 个工具
| 工具 | 入参 | 返回 | 用途 |
|---|---|---|---|
submit_image_task | kind (generate_image/edit_image) + params(与同步工具同 schema) | {task_id, state, submitted_at} | 立即返回,不阻塞会话 |
get_task_result | task_id + 可选 wait(Go duration,严格 ≤ 5m,超出硬报错) | 完整 Task(状态+结果+错误+时间戳) | wait=0s 立即快照;wait>0 long-poll 至终态或超时;超过 5m 的预算请通过重新轮询拼接 |
list_tasks | 可选 states[] / kinds[] / since (RFC3339) / all | {tasks, count} | 默认仅本进程任务;all=true 包含其他 PID 残留。响应体量上限:未过滤时受 OPENPIC_TASK_MAX_RETAINED 约束(默认 1024 → ~1.3 MB,硬上限 100000 → ~10 MB);MCP stdio 不流式,建议高任务量场景下用 since / states 过滤分桶 |
cancel_task | task_id + 可选 hint | 取消后的 Task | 跨 PID 拒绝;终态任务返回当前快照不变 |
调用示例
// 1. 提交一个生图任务,立即拿到 task_id
{
"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{
"name":"submit_image_task",
"arguments":{
"kind":"generate_image",
"params":{"prompt":"a cat","size":"1024x1024","response_format":"file_path"}
}
}
}
// → {"task_id":"tsk_28341_2026...","state":"queued","submitted_at":"..."}
// 2. 轮询(long-poll,最多 30 秒)
{
"jsonrpc":"2.0","id":2,"method":"tools/call",
"params":{
"name":"get_task_result",
"arguments":{"task_id":"tsk_28341_2026...","wait":"30s"}
}
}
// → 终态 Task:state=completed, result.file_path=...
// 3. 列出最近 1 小时的所有 running 任务
{
"jsonrpc":"2.0","id":3,"method":"tools/call",
"params":{
"name":"list_tasks",
"arguments":{"states":["running"],"since":"2026-04-29T10:00:00Z"}
}
}持久化与重启行为
OPENPIC_TASK_DISK_PERSIST=true(默认):每次状态迁移和淘汰都通过temp + fsync + rename原子写盘到/tasks/.json。- 启动时扫描该目录,把
queued/running状态的任务自动转abandoned(hint=process_restart),让重启前正在跑的任务有确定结果,调用方决定是否重新提交。 - 跨 PID 隔离:
list_tasks默认只返回本进程任务;cancel_task跨 PID 拒绝,避免误改他人任务。 OPENPIC_TASK_DISK_PERSIST=false:纯内存模式,重启即丢,适合无写盘权限的容器或 ephemeral 环境。
优雅停机
shutdown 时引擎按以下顺序协同:
- recv loop 退出 → 不再接受新 MCP 请求。
dispatcher.AbandonRunning("shutdown")—— 把所有 running 任务标 abandoned,取消其 ctx;任务 worker 通过 ctx.Done 立即返回。- 关闭 sync MCP work queue → 排空已入队
tools/call。 - 等待 inflight WaitGroup(最长
OPENPIC_SHUTDOWN_TIMEOUT)。 - 关闭 transport。
- main 调
dispatcher.Close()等任务 worker 全部退出。 - 调
taskStore.Close()标记关闭。
任务持久化让客户端在下次启动后还能 get_task_result 看到 state=abandoned,避免悬挂。
上游链路与取消语义
cancel_task 在 MCP 层是确定性的:调用成功后,本进程的 dispatcher 立即把任务状态置为 cancelled、关闭其 ctx、释放 worker 槽。同步工具 (describe_image / compare_images / generate_image / edit_image) 全部使用 http.NewRequestWithContext(ctx, …),因此 ctx 取消会立刻关闭与上游 (OPENPIC_API_BASE_URL) 的 TCP 连接。
但 "取消是否会节省上游配额"取决于上游服务的实现,本项目不做承诺:
| 上游形态 | 收到客户端断连后是否会取消上游计算 / 配额 |
|---|---|
OpenAI 官方 /v1/images/* | OpenAI 官方未公开声明 client-disconnect 是否会回滚配额;保守假设:不会。 |
| OpenAI-Compatible 网关(如 sub2api) | 网关本身有内部 retry / failover;客户端断连不一定传播到上游 OpenAI 调用。许多代理实现会等待上游响应后再决定是否计费。 |
| OAuth 凭证池代理(如 CLIProxyAPI) | 代理收到客户端断连后行为依赖具体实现;外部观察不到的副作用是常态。 |
结论与建议:
- 把
cancel_task看作"在 MCP 这一层立刻释放本地资源(worker 槽 / 内存 / store 配额)"的工具,而不是一个节流上游配额的工具。 - 如果担心上游计费,第一选择仍然是避免发起昂贵请求(通过
list_image_capabilities查看模型边界、或本地参数校验 fail-fast),其次才是cancel_task。 - 上游 safety filter 触发亦不在
cancel_task的语义内:实测同一edit_imageprompt 在同步路径会被上游改写为通用占位水印(单次失败即返回,无法本地重试),切换到submit_image_task异步路径后一次过(约 3 分钟 wall time)。当观察到上游 safety 改写或 502/503 抖动较多时,将submit_image_task列为首选路径——服务端持久化与get_task_result(wait=…)让客户端可以无副作用地等待并重排队,而同步路径每一次失败都会消耗一次tools/call预算。
长耗时调用的超时与上游异常
实测:在 gpt-image-2 路径上,sub2api 的 /v1/images/edits 单次延迟普遍在 70-220 秒;当上游 503 触发 failover 时整体延迟还会叠加。某些复合 prompt(例如 panoramic 2048×2048)可能超过 5 分钟。
| 现象 | 责任层 | 治理位置 |
|---|---|---|
上游网关 (Client.Timeout exceeded while awaiting headers) | 上游服务 / 网络 | 调高 OPENPIC_TIMEOUT(注意:这是 HTTP client 超时,不是 OPENPIC_REQUEST_TIMEOUT 的 MCP 工具调用预算) |
tools/call 在 OPENPIC_REQUEST_TIMEOUT 触发取消 | 本服务(设计) | 调高 OPENPIC_REQUEST_TIMEOUT,或用异步 submit_image_task + get_task_result(wait="5m") 解耦客户端等待 |
| 上游已完成但客户端已断连 | 上游服务 | MCP 层无法补救;用异步任务模型可以救一次——任务状态在服务端持久化,客户端重连后用 get_task_result(task_id) 仍能取到最终结果 |
最佳实践:对所有可能 > 30 秒的图像请求,使用 submit_image_task 而非同步 generate_image。这样即便客户端因 IDE 重启 / 网络抖动 / 5 分钟 long-poll 上限断开,也能在重连后通过 task_id 拿回结果。
API/工具说明
describe_image
分析并描述图片内容。
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
image | string | 是 | 图片数据,支持 Base64 编码、Data URI、HTTP/HTTPS URL 或本地文件路径 |
prompt | string | 否 | 自定义分析提示词,不提供则使用默认提示词 |
detail_level | string | 否 | 描述详细程度:brief、normal(默认)、detailed |
示例请求:
{
"name": "describe_image",
"arguments": {
"image": "https://example.com/image.jpg",
"prompt": "描述这张图片中的主要内容",
"detail_level": "detailed"
}
}本地文件路径示例:
{
"name": "describe_image",
"arguments": {
"image": "/path/to/local/image.jpg",
"detail_level": "normal"
}
}示例响应:
{
"content": [
{
"type": "text",
"text": "这张图片展示了..."
}
]
}compare_images
比较多张图片,分析它们的相似点和差异。
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
images | array | 是 | 图片数组(2-4张),每个元素支持 Base64、URL 或本地文件路径 |
prompt | string | 否 | 自定义比较提示词,不提供则使用默认提示词 |
detail_level | string | 否 | 比较详细程度:brief、normal(默认)、detailed |
示例请求:
{
"name": "compare_images",
"arguments": {
"images": [
"https://example.com/image1.jpg",
"https://example.com/image2.jpg"
],
"prompt": "比较这两张图片的主要差异",
"detail_level": "detailed"
}
}本地文件比较示例:
{
"name": "compare_images",
"arguments": {
"images": [
"/path/to/image1.png",
"/path/to/image2.png",
"/path/to/image3.png"
]
}
}示例响应:
{
"content": [
{
"type": "text",
"text": "这些图片的比较结果:\n\n相似点:...\n\n差异点:..."
}
]
}generate_image
根据文本提示词生成图片。该工具调用 OpenAI-Compatible /images/generations 路由,需要配置 OPENPIC_IMAGE_MODEL。
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
prompt | string | 是 | 图片生成提示词 |
size | string | 否 | 输出尺寸,默认 1024x1024;支持 1024x1024、1024x1536、1536x1024、2048x2048 |
quality | string | 否 | 输出质量,实际取值取决于服务支持情况 |
response_format | string | 否 | 响应格式:file_path、url 或 b64_json,默认 file_path;仅显式选择 b64_json 时返回内联 Base64;若上游在 url 模式返回 Data URI,服务端会自动落盘并返回 file_path |
n | number | 否 | 生成图片数量,当前仅支持 1 |
output_dir | string | 否 | 单次调用的落盘目录,绝对路径,无 .. 段;覆盖 OPENPIC_OUTPUT_DIR;response_format=b64_json 时被忽略 |
filename_prefix | string | 否 | 单次调用的文件名前缀;规则同 OPENPIC_FILENAME_PREFIX;覆盖部署级默认 |
overwrite | boolean | 否 | 单次调用的覆盖策略;覆盖 OPENPIC_OVERWRITE |
示例请求:
{
"name": "generate_image",
"arguments": {
"prompt": "一只橘猫坐在窗边,电影感光影",
"size": "1024x1024",
"response_format": "file_path",
"output_dir": "/var/lib/openpic-mcp/images",
"filename_prefix": "demo",
"n": 1
}
}示例响应:
{
"content": [
{
"type": "text",
"text": "{\n \"images\": [\n {\n \"file_path\": \"/var/lib/openpic-mcp/images/demo-20260429-022045-3f8a91b2.png\",\n \"format\": \"png\"\n }\n ],\n \"created\": 1234567890,\n \"requested\": {\n \"prompt\": \"一只橘猫坐在窗边,电影感光影\",\n \"size\": \"1024x1024\",\n \"response_format\": \"file_path\",\n \"n\": 1,\n \"output_dir\": \"/var/lib/openpic-mcp/images\",\n \"filename_prefix\": \"demo\"\n },\n \"applied\": {\n \"size\": \"1024x1024\",\n \"n\": 1,\n \"response_format\": \"file_path\",\n \"output_dir\": \"/var/lib/openpic-mcp/images\",\n \"filename_prefix\": \"demo\",\n \"overwrite\": false\n },\n \"files\": [\n {\n \"index\": 0,\n \"path\": \"/var/lib/openpic-mcp/images/demo-20260429-022045-3f8a91b2.png\",\n \"size_bytes\": 70,\n \"format\": \"png\"\n }\n ]\n}"
}
]
}edit_image
根据输入图片、文本提示词和可选 mask 编辑图片。该工具调用 OpenAI-Compatible /images/edits 路由,需要配置 OPENPIC_IMAGE_MODEL。
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
image | string | 是 | 待编辑图片,支持本地文件路径、HTTP/HTTPS URL、Data URI 或原始 Base64 |
prompt | string | 是 | 图片编辑提示词 |
mask | string | 否 | 可选 mask 图片,支持本地文件路径、HTTP/HTTPS URL、Data URI 或原始 Base64 |
size | string | 否 | 输出尺寸,默认 1024x1024;支持 1024x1024、1024x1536、1536x1024、2048x2048 |
quality | string | 否 | 输出质量,实际取值取决于服务支持情况 |
response_format | string | 否 | 响应格式:file_path、url 或 b64_json,默认 file_path;仅显式选择 b64_json 时返回内联 Base64;若上游在 url 模式返回 Data URI,服务端会自动落盘并返回 file_path |
n | number | 否 | 编辑结果数量,当前仅支持 1 |
output_dir | string | 否 | 单次调用的落盘目录,绝对路径,无 .. 段;覆盖 OPENPIC_OUTPUT_DIR;response_format=b64_json 时被忽略 |
filename_prefix | string | 否 | 单次调用的文件名前缀;规则同 OPENPIC_FILENAME_PREFIX;覆盖部署级默认 |
overwrite | boolean | 否 | 单次调用的覆盖策略;覆盖 OPENPIC_OVERWRITE |
示例请求:
{
"name": "edit_image",
"arguments": {
"image": "/path/to/input.png",
"prompt": "给这只猫添加一顶红色帽子",
"size": "1024x1024",
"response_format": "file_path",
"output_dir": "/var/lib/openpic-mcp/images",
"n": 1
}
}示例响应:
{
"content": [
{
"type": "text",
"text": "{\n \"images\": [\n {\n \"file_path\": \"/var/lib/openpic-mcp/images/edit-20260429-022146-8c11e2af.png\",\n \"format\": \"png\"\n }\n ],\n \"created\": 1234567890,\n \"requested\": {\n \"prompt\": \"给这只猫添加一顶红色帽子\",\n \"size\": \"1024x1024\",\n \"response_format\": \"file_path\",\n \"n\": 1,\n \"output_dir\": \"/var/lib/openpic-mcp/images\"\n },\n \"applied\": {\n \"size\": \"1024x1024\",\n \"n\": 1,\n \"response_format\": \"file_path\",\n \"output_dir\": \"/var/lib/openpic-mcp/images\",\n \"filename_prefix\": \"edit\",\n \"overwrite\": false\n },\n \"files\": [\n {\n \"index\": 0,\n \"path\": \"/var/lib/openpic-mcp/images/edit-20260429-022146-8c11e2af.png\",\n \"size_bytes\": 70,\n \"format\": \"png\"\n }\n ]\n}"
}
]
}图片生成与编辑兼容性说明
以下说明仅适用于 generate_image 与 edit_image,对 describe_image / compare_images 不生效。
response_format 语义
response_format 是纯 MCP 客户端侧的交付方式:
file_path(默认):openPic-mcp 把上游返回的 base64 字节落到本地临时文件,客户端只看到file_path,避免在 MCP 消息中传输大段 base64。b64_json:openPic-mcp 在响应中保留 base64 字段,适用于客户端需要直接拿到字节的场景。url:等同于file_path,仅在上游返回的是真正可访问的 URL(非 data URI)时才保留为 URL。GPT image 等模型默认只返回 base64,因此通常会被 openPic-mcp 落盘为file_path。
无论入参选哪一种,openPic-mcp 都不会把 response_format 转发给上游 API。社区已确认 GPT image models 在 images/edits 上传该字段会触发与“模型不支持”混淆的错误,因此 server 内部一律省略。
size 与 aspect_ratio
size 默认仅信任 OpenAI 官方 enum:1024x1024、1024x1536、1536x1024。2048x2048 仅在部分 OpenAI-Compatible 代理上可用,原生 OpenAI 不一定支持。
为了避免直接面对像素值,可以使用 aspect_ratio:
1:1→1024x10244:3→1536x10243:4→1024x153616:9→1536x1024(最近的横向预设)9:16→1024x1536(最近的纵向预设)auto→ 留空 size,由上游决定
当 size 与 aspect_ratio 同时给出时,size 优先。
output_format
output_format 用于控制上游生成图片的编码格式(png / jpeg / webp),openPic-mcp 会原样转发到上游。该字段是可选的,留空则使用上游默认(通常为 png)。
声明 vs 实际:output_format是 advisory 字段,openPic-mcp 无法强制兑现。社区已确认部分 OpenAI-Compatible 实现会静默吞掉output_format: - OpenAI 官方gpt-image-1在/v1/images/edits端点对output_format=webp直接返回 400"Supported values are: 'png' and 'jpeg'"。 - 多个第三方代理(如 sub2api)会返回成功响应但实际内容仍为 PNG。 为此 openPic-mcp 会在每张返回图片上做 magic bytes 检测,并通过两条额外字段告诉调用方真实情况: -images[i].format:实际检测到的格式(png/jpeg/webp等),文件扩展名也按这个值打。 -warnings[]:当请求的output_format与检测格式不一致时附加的提示(例如images[0]: requested output_format="webp" but upstream returned "png"; saved as .png)。 调用list_image_capabilities可拿到output_format_enforcement: "advisory"与output_format_notes完整披露。
输出路径策略(P1)
generate_image / edit_image 在 response_format=file_path(默认)或 url 模式下会把上游字节落到本地磁盘。落盘行为遵循下列优先级:
- 单次调用入参
output_dir/filename_prefix/overwrite。 - 部署级环境变量
OPENPIC_OUTPUT_DIR/OPENPIC_FILENAME_PREFIX/OPENPIC_OVERWRITE。 - 默认值:
os.TempDir()/openpic-mcp/、工具上下文generate/edit、不覆盖。
文件名模板为 -YYYYMMDD-HHMMSS-.,其中:
- `
来自 magic-byte 检测出的真实格式(png/jpeg/webp/ ...),与output_format` 是否兑现解耦。 - `
是 4 字节随机数的十六进制;overwrite=false模式同名冲突时追加-2/-3` 直到唯一。
output_dir 必须是绝对路径且不含 .. 段,否则在到达上游前直接返回错误。filename_prefix 限制在 [A-Za-z0-9._-]、最长 32 字符且不能以 . 开头。
结构化结果合同(P1)
generate_image / edit_image 的响应在保留 images / created / warnings 兼容字段之外,新增以下结构化字段,便于 MCP 客户端区分"调用方传入"与"服务端实际生效":
requested:调用方实际传入的关键参数(prompt/size/aspect_ratio/quality/output_format/response_format/n/output_dir/filename_prefix/overwrite)。未传字段用omitempty省略。applied:发往上游的参数(size/quality/output_format/n)以及最终生效的交付参数(response_format/output_dir/filename_prefix/overwrite)。files[]:每张落盘文件的index/path/size_bytes/format。response_format=b64_json时该字段被省略。usage:仅在上游响应里携带usage时透传input_tokens/output_tokens/total_tokens,缺字段以omitempty省略。openPic-mcp 不会伪造任何 token 数。
内联 payload 字节预算(P1)
OPENPIC_MAX_INLINE_PAYLOAD_BYTES 默认 1048576(1 MiB)。
response_format=b64_json模式下若解码后超阈:直接返回isError工具结果,提示改用file_path或下调quality/size,不会静默切换交付方式。response_format=file_path/url模式下若超阈:照常落盘,但响应里会附加一条warnings[],便于调用方主动调参或扩大预算。- 设置为
0或负值会被忽略,回退到默认,避免误关 guard。
502 / upstream_error 误读指南
OpenAI-Compatible 图像上游可能把多种失败都包装为 502 upstream_error。常见情况包括:
- 上游服务临时不可用,可稍后重试。
- 请求参数与目标模型不兼容,例如不支持的
size、response_format或模型路由。 - 图像编辑端点对输入图片本身触发内容审核,但上游没有返回明确的 moderation 错误。
如果同一张图片在多个无害 prompt 下反复 edit 失败、而其他图片同时可以 edit 成功,可能是上游 image moderation 触发。客户端无法可靠区分该情况,建议停止重试并更换输入图片。openPic-mcp 不会自动重试 502/503/504,避免在不可恢复场景下扩大错误面。
图片生成与编辑耗时
图片生成和编辑请求会等待上游 OpenAI-Compatible 服务完成推理后再返回。部分模型(例如高质量图片生成模型)单次 1K 图片可能需要约 1-2 分钟,2K 图片可能需要约 2-4 分钟。建议将 OPENPIC_TIMEOUT 保持为默认 5m 或按实际服务耗时调大,避免服务端在上游仍在推理时提前超时。
支持的图片格式
- JPEG (.jpg, .jpeg, .jpe, .jfif)
- PNG (.png)
- WebP (.webp)
- GIF (.gif)
- BMP (.bmp, .dib)
- TIFF (.tif, .tiff)
- ICO (.ico)
- HEIC/HEIF (.heic, .heif)
- AVIF (.avif)
- SVG (.svg, .svgz)
注意:实际支持情况取决于您使用的 Vision API 服务。部分格式(如 HEIC、AVIF、SVG)可能不被所有 API 支持。
图片输入方式
- Base64 编码:直接传入 Base64 编码的图片数据
- Data URI:
data:image/jpeg;base64,/9j/4AAQ... - HTTP/HTTPS URL:
https://example.com/image.jpg - 本地文件路径:
/path/to/local/image.jpg或C:\path\to\image.png(Windows)
注意:本地文件路径支持绝对路径和相对路径。系统会自动检测文件的 MIME 类型。
开发指南
项目结构
openPic-mcp/
├── cmd/vision-mcp/ # 主程序入口
│ ├── main.go
│ └── main_test.go
├── internal/
│ ├── config/ # 配置管理 + slog Logger 构造器
│ ├── errors/ # 错误定义
│ ├── image/ # 图片处理(本地文件、MIME检测)
│ ├── protocol/ # 协议层(JSON-RPC、MCP、CancellationRegistry)
│ ├── provider/ # Provider 层
│ │ └── openai/ # OpenAI-Compatible Provider
│ ├── retry/ # 重试机制
│ ├── server/ # MCP 服务器引擎(worker pool / 队列 / 优雅停机)
│ ├── service/tool/ # 工具管理器
│ ├── tools/ # 工具实现
│ │ ├── describe.go # describe_image 工具
│ │ ├── compare.go # compare_images 工具
│ │ ├── generate.go # generate_image 工具
│ │ └── edit.go # edit_image 工具
│ └── transport/ # 传输层(stdio)
├── pkg/types/ # 公共类型定义
├── .env.example # 环境变量示例
├── Dockerfile # Docker 构建文件(预留)
├── docker-compose.yml # Docker Compose 配置(预留)
├── go.mod # Go 模块定义
└── README.md # 项目文档构建
# 构建可执行文件
go build -o openPic-mcp ./cmd/vision-mcp
# 构建 Docker 镜像
docker build -t openpic-mcp:latest .测试
# 运行所有测试
go test ./...
# 运行测试并显示详细输出
go test -v ./...
# 运行测试并生成覆盖率报告
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html代码格式化
# 格式化代码
go fmt ./...
# 检查代码格式
gofmt -d .代码检查
# 运行 go vet
go vet ./...路线图
v1.0(当前版本)
- ✅ MCP 协议核心实现(stdio 传输)
- ✅ OpenAI-Compatible Vision API 支持
- ✅ describe_image、compare_images、generate_image 和 edit_image 工具
- ✅ 本地文件路径支持
- ✅ 多格式图片支持(10种格式)
v1.x(规划中)
- 🔲 图片压缩功能(已设计,待实现)
- 🔲 HTTP/SSE 传输支持
- 🔲 Docker 容器化部署(依赖 HTTP 传输)
- 🔲 发布到 npm,支持
npx方式调用 - 🔲 更多 Vision 工具(UI 分析、代码提取等)
v2.0(远期规划)
- 🔲 托管服务,用户无需部署即可使用
- 🔲 多 Provider 支持(Anthropic、Google 等)
许可证
MIT License
Copyright (c) 2024
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
