一个图片接口的 MCP 化:从能调用到能发布
一个图片接口的 MCP 化:从能调用到能发布
一个图片接口真正变成可复用工具,靠的不是多包一层协议,而是重新思考它应该对谁负责、允许自己知道什么,以及哪些事情必须拒绝。
agnes-image-mcp 从一个具体项目里的图片调用需求开始,最后成为基于 MCP stdio 的 npm 包。当前版本是 0.1.7,要求 Node.js >=20,源码与发布记录放在 GitHub。
项目提供四个工具,范围保持得很窄。
| 工具 | 作用 | 关注的问题 |
|---|---|---|
generate_image | 生成单张图片 | 如何统一输入与输出 |
generate_images | 批量生成图片 | 如何处理顺序、失败与额度 |
download_image | 下载 HTTPS 图片 | 如何控制网络与文件边界 |
validate_image | 校验本地图片 | 如何提供可信的本地结果 |
重要
MCP 的价值在于把能力交给更多调用方,同时把业务耦合留在原来的位置。
先决定它不负责什么
一个独立工具首先要有清楚的拒绝范围。图片 MCP 只处理图片生成、批量生成、下载和本地校验,具体项目的业务流程、素材编排和后续处理都留给上层完成。
这条边界带来三个直接结果。
- 上层项目可以替换图片能力,而不必改动自身业务流程。
- MCP 客户端只需要理解稳定的工具参数,不必理解 Agnes 请求体。
- Provider 可以随着上游变化调整,调用方接口保持相对稳定。
MCP 进程使用 stdio 与客户端通信,不监听 HTTP 端口。这个选择适合本地 AI 工具直接启动,也让密钥可以停留在父进程环境中。协议消息走 stdout,诊断日志走 stderr,协议通信与普通日志互不干扰。
抽象的重点是稳定调用方语言
上游接口会变化,调用方的意图相对稳定。调用方关心的是提示词、尺寸、比例、参考图和返回形式,Provider 负责把这些信息翻译成 Agnes 所需的请求结构。
interface ImageGenerationRequest {
prompt: string;
size: '1K' | '2K' | '3K' | '4K';
ratio?: '1:1' | '3:4' | '4:3' | '16:9' | '9:16' | '2:3' | '3:2' | '21:9';
model?: string;
images?: string[];
output?: 'url' | 'base64';
}这里有一个值得保留的设计判断。内部模型不应该照搬上游字段。images 表达的是调用方的参考图,output 表达的是调用方需要的结果形式,至于它们在 Agnes 请求中的具体位置,应当由 Provider 自己承担。
好的抽象会隐藏变化,但不会隐藏能力边界。
为什么批量接口使用 items[]
批量生成使用 items[],每一项都拥有单图生成所需的参数,还可以通过 id 对应原始任务。这个结构更接近调用方的任务集合,也方便未来增加单项状态、错误信息和结果元数据。
默认串行执行,失败后停止。这个默认值体现的是资源意识。图片生成会消耗上游额度,继续执行并不总是比停下来更好。调用方明确设置 continueOnError=true 后,服务才继续处理后续项目。
| 设计选择 | 背后的考虑 |
|---|---|
使用 items[] | 保留每个任务的独立上下文 |
| 默认串行 | 先保证顺序和可预测性 |
| 默认失败停止 | 避免错误扩大为额度消耗 |
| 返回 skipped 统计 | 让未执行和执行失败可以区分 |
为什么配置只保留两个环境变量
AGNES_API_KEY 属于进程级凭据,AGNES_MODEL 属于默认模型。尺寸、比例、参考图和输出格式属于一次调用,应该跟着请求走。
把所有参数都塞进环境变量,会让一个进程逐渐绑定某一种业务。配置项越多,复用成本越高,排查时也越难判断某个值来自哪里。
凭据应当停在运行环境
AGNES_API_KEY 不进入源代码、提示词、日志、Issue、MCP 配置文件和 Git 历史。项目只读取父进程传入的环境变量,也不自动加载 .env。
协议边界之外,还有资源边界
图片服务同时接触远程网络和本地文件。只要工具拥有这两种能力,就需要明确回答两个问题。它可以访问哪里,以及它可以写入哪里。
download_image 只接受 HTTPS 图片地址,目标文件限制在当前工作目录下的相对路径内。服务会检查重定向目标、DNS 解析结果、私有地址、回环地址、响应大小、请求超时和图片文件签名。
这套设计表达的是一种责任分配。调用方可以提出下载请求,服务端必须负责限制请求的影响范围。模型驱动的调用尤其如此,安全条件不能只写在 README 里,也不能期待每个客户端都正确执行。
validate_image 的边界更窄。它只处理本地路径,不接受远程地址,不访问网络,也不修改文件。生成、下载和校验分别拥有不同的权限范围,工具职责因此更容易理解。
文件系统能力必须收敛
允许任意 URL 和任意路径,会同时放大内网探测与本地文件写入的影响。安全边界应当由服务端强制执行,并且尽量在工具设计阶段确定,而不是等出现问题后补规则。
限流体现的是资源观
Agnes 免费版按图片尺寸分桶限流,当前基线如下。
| 尺寸 | 每分钟请求数 | 最短间隔 |
|---|---|---|
1K | 20 | 3 秒 |
2K | 10 | 6 秒 |
3K | 1 | 60 秒 |
4K | 1 | 60 秒 |
限流不只是延迟几秒。它需要表达请求与资源之间的关系。当前设计让每个尺寸拥有独立的计数,真实上游请求才消耗额度,失败不退款。收到 429 时优先遵循 Retry-After,没有明确等待时间时再使用有限的指数退避。
这套限流状态只存在当前 MCP 进程内。多个进程或多台机器之间不会共享状态。这个限制必须被明确写出来,因为内存限流适合本地单进程工具,不能直接被当成分布式配额系统。
限制本身也是接口契约的一部分。
测试使用可控时钟验证不同尺寸的独立行为、批量顺序和失败处理,不需要真的等待一分钟。可测试性因此成为设计的一部分,而不只是测试阶段的技巧。
MCP 工具需要比函数多做一点
普通函数通常只需要返回结果。MCP 工具还要让调用方知道自己能做什么、需要什么参数,以及失败时该如何理解结果。
项目统一使用严格 Schema,拒绝未声明字段。工具结果包含稳定的 code、message 和 data,失败时设置错误状态,同时避免回显 Authorization 等敏感信息。
这种统一格式降低了客户端处理成本。调用方可以根据错误码决定重试、停止或提示用户,而不必解析一段不稳定的异常文本。
工具输入
↓ Schema 校验
服务层编排
↓ 限流与可恢复重试
Provider 或本地服务
↓ 结果标准化
MCP 响应工具描述、outputSchema、structuredContent 和优雅退出等能力,解决的是工具与客户端之间的协作问题。它们不会改变图片生成逻辑,却会影响一个 MCP 是否容易被发现、调用和维护。
测试的重点是边界,不是数量
项目使用本地 Mock 覆盖大多数场景,重点验证调用契约和边界行为。
- 输入字段能否正确映射到 Provider。
- URL、Base64、参考图和多图输入能否稳定返回。
- 批量任务的顺序、停止策略和部分结果是否明确。
- 限流、超时、429 和网络错误是否按约定处理。
- 下载器是否拒绝危险地址、危险路径和错误文件内容。
- MCP 工具是否能被注册、调用并返回结构化结果。
真实请求只用于确认 mock 无法确认的上游契约,自动化测试仍然承担主要回归职责。这样既减少对外部服务的依赖,也避免把额度消耗当成测试成本。
发布过程也是产品设计的一部分
一个 npm 包的完成标准,不只是源码能够运行。使用者还需要拿到正确的入口、完整的运行时文件和可复现的版本。
项目将类型检查、测试、构建、版本校验、npm 包内容检查、stdio 冒烟测试和 tarball clean-install 测试串成发布门禁。
发布认证采用 GitHub Actions 的 npm Trusted Publishing 和 OIDC,不长期保存 NPM_TOKEN。工作流通过身份声明获得发布资格,并使用 provenance 发布包。
permissions:
contents: read
id-token: write
- name: Publish package
run: npm publish --access public --provenance这里的思考可以延伸到所有开源工具。发布系统不是项目外部的行政流程,它决定了版本如何产生、包如何被验证、用户拿到的内容是否和源码一致。
固定版本更适合复现
使用时可以固定 agnes-image-mcp@0.1.7。查看源码、Issue 和版本记录,可前往 agnes-image-mcp GitHub 仓库。
继续扩展时,先保护边界
后续可以增加新的图片 Provider,也可以讨论 HTTP 传输、更多输出形式或更细的任务控制。每次扩展都需要先检查它改变了哪一层责任。
| 扩展方向 | 应当先回答的问题 |
|---|---|
| 新 Provider | 内部接口是否足以隔离供应商差异 |
| HTTP 传输 | 鉴权、部署和远程访问边界由谁负责 |
| 更高并发 | 配额、顺序和失败重试如何重新定义 |
| 更多文件操作 | 工作目录和用户授权是否仍然足够收敛 |
| 新输出格式 | 调用方是否需要承担额外解析成本 |
如果一个新需求只能通过把业务字段继续塞进 MCP 来实现,说明职责边界需要重新讨论。稳定的工具不会试图知道所有业务,它只负责把自己的那一小块能力做好,并把边界说清楚。
这次项目留下的思考也因此比较简单。抽离能力时,先确认它是否拥有独立的输入和输出;设计接口时,让调用方语言和上游语言分开;涉及网络与文件时,把权限范围写进工具行为;准备发布时,把最终包当成产品验证。
agnes-image-mcp 已经发布到 npm。它现在可以被不同的 AI 客户端启动,也可以被不同类型的项目复用。后续实现可以继续变化,但这几条边界应当尽量保持稳定。