多模态工具设计:图像、音频与文档内容的返回契约

系统讲解 MCP 多模态工具的设计方法:content 数组中的 text/image/audio/embedded resource 四种块类型、base64 与资源链接两种传输方式的取舍、图像尺寸与 token 成本的换算、截图类工具的压缩与降采样策略、模型能力探测与降级、以及大二进制内容返回时的分片、缓存与审计陷阱。

绝大多数 MCP 工具返回的是文本:查询结果、文件内容、日志片段。但真实工作里有大量信息天生不是文本——界面长什么样、图表趋势如何、这段音频里有没有异常、这份 PDF 的表格结构是什么。把它们硬转成文本(describe_screenshot 返回一段自然语言描述)会丢掉细节,而且转述本身又要花一次模型调用。

MCP 的 content 数组允许工具直接返回图像与音频块,让「工具看到的」和「模型看到的」是同一份数据。本文要回答的是:四种内容块怎么用、base64 内联和资源链接各自适合什么场景、一张 1080p 截图到底吃掉多少 token、模型不支持视觉时怎么降级、以及大二进制返回时最容易踩的坑。

1. 内容块的四种类型

tools/call 的返回值是 content 数组,可以混装多种块:

{
  "content": [
    { "type": "text", "text": "页面已渲染,检测到 3 处对比度不足" },
    {
      "type": "image",
      "data": "<base64>",
      "mimeType": "image/png"
    },
    {
      "type": "audio",
      "data": "<base64>",
      "mimeType": "audio/wav"
    },
    {
      "type": "resource",
      "resource": { "uri": "file:///tmp/report.pdf", "mimeType": "application/pdf" }
    }
  ]
}

1.1 各类型的适用场景

块类型载荷典型场景成本
text字符串结论、状态、结构化 JSON低
imagebase64 + mimeType截图、图表、UI 状态、OCR 前的原图中高(视觉 token)
audiobase64 + mimeType语音转写前的原音频、异常音频检测高
resourceURI + mimeType大文件、可复用产物、需二次读取的内容取决于后续是否读取

1.2 混装是常态

一次工具调用同时返回「结论 + 证据」通常比只返回结论更有用:

截图工具:
  text   → "登录页已加载,表单可见"
  image  → 实际截图(模型可自行核对细节)

顺序上把 text 放前面:多数客户端在渲染与上下文注入时按顺序处理,先给结论能让模型快速建立预期。

2. base64 内联 vs 资源链接

2.1 两种方式的对比

维度内联 base64资源链接(resource / resource_link)
模型能否直接看到是需客户端或后续工具读取
传输成本体积 ×1.33(base64 膨胀)仅 URI
上下文成本立即计入 token按需计入
可复用性无(一次性)高(URI 可再次读取)
适用体积建议 < 500KB无上限

2.2 决策规则

需要模型"看图判断" → 内联 image(缩到必要分辨率)
只是"产物留档/供后续处理" → resource_link
同一图片会被多次使用 → resource_link + 缓存
图片超过 1MB → 先压缩/降采样,仍超则改链接
{
  "content": [
    { "type": "text", "text": "已生成 12 页报表" },
    {
      "type": "resource_link",
      "uri": "file:///tmp/report-2026-10.pdf",
      "name": "月度报表",
      "mimeType": "application/pdf",
      "size": 4821133
    }
  ]
}

resource_link 只给引用不给内容,是大产物的默认选择。客户端可以把它渲染成可点击的附件,模型则在需要时再调工具读取指定页。这种「先给引用、按需取内容」的模式,是控制上下文膨胀最有效的手段,与 https://plumephp.com/mcp-resources-prompts/ 中资源按需注入的思路一致。

3. 图像的 token 成本

3.1 粗略换算

视觉模型通常按图像块计费。以常见的「长边归一 + 分块」策略估算:

图像 token ≈ (宽/块宽) × (高/块高) × 每块 token + 基础开销

经验值(长边上限 1568px 的模型):
  512×512      ≈ 250 ~ 400 token
  1024×768     ≈ 700 ~ 1100 token
  1920×1080    ≈ 1300 ~ 1600 token
  4K 截图      ≈ 被自动降采样,但仍按降采样后的尺寸计费

3.2 一张截图有多贵

对比一下:一张 1920×1080 的 PNG 截图约 1500 token,而一段 1500 token 的文本大约是 1000 个汉字。也就是说,返回一张全屏截图 ≈ 返回一篇千字文章。如果一个 Agent 循环里每步都截图,20 步就是 30000 token 的视觉开销。

优化手段按性价比排序:

手段效果代价
只截元素区域而非全屏减少 60%~90%需可靠的元素定位
降采样到长边 1024减少 50%~70%小字可能不可辨
转 JPEG(quality 80)传输减少 60%~80%token 不变,仅省带宽
转 WebP传输减少 70%~85%部分模型不支持该格式
只截变化区域依场景实现复杂

注意第二、四行的差别:JPEG/WebP 主要省的是传输与内存,不是 token。token 由解码后的像素尺寸决定,所以「压缩格式」和「降采样」是两件不同的事。真正的省钱手段是降低分辨率或缩小截取范围。

3.3 降采样实现

import sharp from "sharp";

async function toContentBlock(png: Buffer, maxEdge = 1024): Promise<ImageBlock> {
  const meta = await sharp(png).metadata();
  const long = Math.max(meta.width ?? 0, meta.height ?? 0);
  const scale = long > maxEdge ? maxEdge / long : 1;
  const out = scale < 1
    ? await sharp(png).resize({ width: Math.round((meta.width ?? 0) * scale) }).png({ compressionLevel: 9 }).toBuffer()
    : png;
  return { type: "image", data: out.toString("base64"), mimeType: "image/png" };
}

截图类工具的完整交互设计(等待渲染稳定、元素定位、a11y 树替代方案)见 https://plumephp.com/mcp-browser-web-tools/——其中「能用文本结构表达的,优先用文本」这条原则,同样适用于一切多模态工具。

3.4 什么时候不该返回图像

多模态不是「能传图就传图」。下面几种情况,文本比图像更合适:

情况为什么不用图像替代方案
内容本身是文本(日志、代码、表格)转成像素后模型读错字符直接返回文本
只需判断「有没有」图像成本远高于布尔值返回 {"found": true}
需要精确定位坐标视觉模型的坐标回归不精确配合 DOM / a11y 树返回结构化坐标
需要在后续轮次反复引用每轮都要重新传图落盘 + resource_link
图像含大量小字(如整页 PDF)降采样后不可读先 OCR 出文本,图作为证据附上

第 5 条是最容易翻车的场景:一份 A4 扫描件缩到 1024 长边后,正文小字基本糊成一团,模型只能靠猜。正确顺序是先文本后图像——OCR 给出可检索、可引用的文本,图像块只作为「原证据」补充。

PDF 处理推荐流程:
  1. 抽取文本层(若有)→ 直接返回文本
  2. 无文本层 → OCR → 返回文本 + 每页缩略图(可选)
  3. 图表页 → 单独渲染为图像块(图表信息在像素里)

4. 模型能力探测与降级

4.1 不是所有模型都能看图

MCP 协议层不关心模型能力,但工具设计必须关心。服务器无法直接查询客户端用的是什么模型,可行做法有三种:

1. 客户端能力/元数据:部分客户端在 initialize 的 clientInfo 里暴露模型信息
2. 工具参数显式声明:入参加 modality: ["text","image"] 由 Agent 侧填写
3. 结果双轨:同时返回 text 摘要与 image,让不支持视觉的模型至少能用摘要

方案 3 最稳:它不依赖任何探测,代价是 token 翻倍。折中做法是让 include_image 成为可选参数,默认返回文本摘要。

4.2 双轨返回示例

{
  "content": [
    { "type": "text", "text": "图表:Q3 营收 1.2 亿,环比 +18%;Q1/Q2 分别为 0.8/1.0 亿。趋势向上,Q3 斜率最大。" },
    { "type": "image", "data": "<base64>", "mimeType": "image/png" }
  ]
}

文本部分是信息等价摘要,不是「见下图」这种占位。这样即便图像块被客户端丢弃,模型仍能基于文本完成推理。这与多模态模型本身的输入组织方式有关,可参考 多模态图像理解 。

4.3 音频的额外约束

音频比图像更贵、更少见支持:

- 单次返回建议 < 30 秒,长音频切成片段并按需取
- mimeType 用 audio/wav 或 audio/mpeg,避免冷门格式
- 转写优先:能先转文本就转,把音频作为"原证据"附上
- 采样率 16kHz 单声道足够语音场景,别传 48kHz 立体声

音频推理与部署侧的约束可参考 多模态推理部署 中的显存与批处理章节。

5. 大二进制内容的工程处理

5.1 三类失败模式

失败表现根因
上下文爆炸一次返回 50MB PDF 的 base64未设体积上限
传输超时stdio/HTTP 传输长时间无响应大块数据未分片、无进度
内存耗尽服务器 OOM全量读入内存再编码

5.2 硬上限与分片

const MAX_INLINE = 512 * 1024;      // 512KB 内联上限
const MAX_CHUNK  = 256 * 1024;      // 分片大小

async function readFileBlocks(p: string): Promise<ContentBlock[]> {
  const stat = await fs.stat(p);
  if (stat.size <= MAX_INLINE) {
    return [{ type: "image", data: (await fs.readFile(p)).toString("base64"), mimeType: guess(p) }];
  }
  // 超限:只返回链接,由调用方按需分段读取
  return [{
    type: "resource_link",
    uri: pathToFileURL(p).href,
    mimeType: guess(p),
    size: stat.size,
  }];
}

原则:服务器不该把「大」当成自己的问题。超过内联上限就返回引用,把「要不要读、读哪一段」的决策交给调用方。分段读取工具应支持 offset/length 参数,并保证对同一文件多次调用结果一致。

5.3 长任务与进度

多模态工具往往耗时(渲染、转码、OCR),应在返回前先推送进度通知,避免客户端在渲染完成前就判定超时。进度上报与取消语义是长任务工具的通用课题,核心约束是:进度通知必须在请求的令牌有效期内发出,响应一旦返回,令牌立即失效。

6. 缓存、隐私与审计

6.1 缓存

图像/音频的生成通常昂贵(渲染、转码),适合按内容哈希缓存:

cache_key = sha256(输入参数 + 工具版本 + 渲染配置)
命中 → 复用已生成的 base64 或已落盘文件 URI
失效 → 输入变化、工具版本变化、TTL 到期

注意缓存的是产物文件而非 base64 字符串:字符串缓存会让内存随会话线性增长,落盘 + 返回 resource_link 更可控。token 侧的预算控制与结果缓存策略见 https://plumephp.com/mcp-cost-optimization-token/。

6.2 隐私

- 截图可能包含用户隐私(聊天记录、邮箱、令牌)→ 落盘前评估是否脱敏
- 返回 resource_link 时,URI 不应包含明文敏感参数
- 音频可能包含可识别身份的声音 → 明确告知用户"将上传音频"
- 缓存文件要有权限控制(0600)与过期清理

6.3 审计记录什么

记录:工具名、参数摘要(不含 base64)、产物哈希、体积、mimeType、耗时
不记录:base64 内容本身、完整文件路径(按需脱敏)

审计的意义是「能复现这次调用」,不是「把内容再存一份」。

7. 常见陷阱

陷阱症状解决
全屏截图当默认token 快速耗尽默认只截相关区域,降采样
用 JPEG 当省钱手段传输省了、token 没变降低分辨率才省 token
只返回图像无文本无视觉模型完全不可用双轨返回文本摘要
大文件内联 base64上下文爆炸 / OOM超限改 resource_link
音频传高采样率立体声体积与成本翻倍16kHz 单声道 + 转写优先
长任务无进度客户端判超时先推 progress 通知
缓存 base64 字符串内存线性增长缓存文件 + 返回链接
截图未脱敏隐私泄露落盘前评估与脱敏

8. 小结

多模态工具的设计可以概括为「能给引用就不给内容,能给文本就不给像素」:

层面要点
块类型text / image / audio / resource / resource_link 按需混装
传输需模型看图 → 内联;留档或复用 → 链接
成本降采样降 token,压缩格式只降带宽
兼容文本摘要 + 图像双轨,覆盖无视觉模型
工程512KB 内联上限,超限返回链接;长任务先报进度
治理缓存产物文件、脱敏、审计记哈希不记内容

把多模态能力接进 MCP 的收益很直接:工具不再需要「用文字描述世界」。但代价同样直接——每一张图都是一次上下文消费。把体积上限、分辨率预算、降级路径三件事定死,多模态工具才不会变成 Agent 的奢侈品。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「AI工程」更多文章

  1. IDE 与编辑器集成:stdio 生命周期、工作区上下文与诊断回写
  2. 工具版本与兼容性治理:能力协商、Schema 演进与灰度下线
  3. 流式响应与进度通知:progress token、日志通知与背压处理