做文档型 Agent 的团队,迟早会遇到一个很朴素但很烦的问题:PDF 不是一段文本。
合同、巡检报告、故障复盘、审计材料、云资源账单、SOP 手册,表面上都可以被解析成文本,再丢给 RAG 或上下文拼接。但真实应用里,用户经常不是问“这份 PDF 里有哪些字”,而是在做更复杂的动作:
看第 3 页的表格是不是和摘要一致 找出表单字段并补齐缺项 在某几页上批注风险点 对比一处截图、一个图例和旁边的说明 只打开前两页就先判断这份材料是否值得继续处理
如果工具只能返回一大段文本,Agent 就会被迫把“文档理解、界面交互、用户确认、上下文压缩”全部塞进一次模型调用里。结果通常很拧巴:上下文变胖、首屏变慢、用户看不到证据,出了错也很难定位到底是解析错了、检索错了,还是模型理解错了。
这也是 MCP Apps 最近这条更新值得关注的地方。MCP Apps 不是再造一个 Agent 框架,而是把 MCP 工具的输出形态从“文本和结构化数据”扩展到了“可嵌入的交互 UI”。在 2026 年 4 月 27 日发布的 v1.7.1 里,PDF Server 示例继续往工程化方向推进:display_pdf 不再为了抽取表单信息而整份下载和解析 PDF,而是通过 range transport 做增量读取;查看器侧也把注释和字段扫描延后到页面真正渲染时再做。
这个变化看起来是一个示例优化,但背后对应的是文档型 AI 应用的一条关键分界线:
Agent 不一定要把所有文档内容都吞进上下文,它可以调用工具拿到必要结构,再把可视化确认交给内嵌界面完成。
文档工具为什么容易把 Agent 拖慢
很多团队的第一版 PDF Agent 架构大概长这样:
PDF URL -> 下载整份文件 -> 服务端解析文本、表格、表单、图片 -> 生成一份很大的 JSON 或 Markdown -> 塞进模型上下文 -> 模型回答用户问题 这个链路适合离线索引,但不适合交互式任务。
第一,首轮响应会被完整下载和完整解析卡住。哪怕用户只是想看第一页,工具也可能先把后面几十页都处理完。
第二,解析结果很难表达“证据位置”。模型说第 7 页有一个风险项,用户还要另外打开 PDF 去找。排障、审计和变更评审场景里,这种跳转成本会直接降低可信度。
第三,PDF 里的表单和注释不是普通文本。它们有坐标、字段名、控件类型、默认值、用户修改痕迹。强行压成 Markdown,会丢掉很多操作所需的状态。
第四,工具输出越大,Agent 越容易在上下文预算里和历史对话、检索片段、工具结果互相挤占。最后不是模型“不聪明”,而是输入已经变成了噪声堆。
所以,文档型 Agent 的核心问题不是“能不能解析 PDF”,而是:
能不能只处理当前任务真正需要的那部分内容,并且让用户在同一个对话环境里看见、编辑和确认证据。
MCP Apps 改变的是工具结果的形态
传统 MCP 工具一般返回两类东西:
text -> 给模型读 json/data -> 给模型或客户端消费 这当然足够覆盖很多后端能力,比如查日志、查指标、执行 SQL、读工单字段。但一旦工具结果本身需要人机协同,就会很别扭。比如图表需要缩放,表单需要填写,PDF 需要翻页,拓扑图需要点选,视频需要定位到某一帧。
MCP Apps 的做法是让工具声明一个 ui:// 资源,把 HTML 界面和工具关联起来。工具被调用后,Host 可以在沙箱 iframe 中渲染这个界面,并通过消息通道把工具数据传给 UI。UI 也可以在受控范围内再调用 Host 暴露的能力。
简化之后,它像这样:
MCP tool -> 返回结构化结果 -> 关联 ui://pdf-viewer Host -> 获取 UI resource -> 沙箱渲染 iframe -> 将工具结果推给 UI 用户 -> 在对话内翻页、批注、查看字段 -> 必要时再触发工具调用 对 AI 应用开发来说,这不是“给工具结果加个前端皮肤”。它会改变上下文工程的边界:
模型上下文里只放决策需要的摘要、字段和用户意图 大文件、页面渲染、局部状态留在工具和 UI 层 用户确认不再依赖模型复述,而是直接发生在证据界面里 工具调用结果可以从“一次性文本块”变成“可持续交互状态”
这对智能运维尤其有用。很多运维材料并不是干净的 API 结果,而是 PDF 报告、截图、拓扑、变更单附件、审计表格。让 Agent 把这些材料全部读成文本,既慢,也不可靠。
PDF Server 这次更新真正解决了什么
v1.7.1 的 PDF Server 变化可以拆成两层看。
第一层是服务端的增量读取。过去 display_pdf 为了判断表单字段,容易把整份 PDF 下载并解析一遍。新的实现把 PDF.js 的读取方式改成 range transport,让服务端只拉取当前探测需要的字节范围。对没有表单的 PDF,探测可以在只读取一部分文件的情况下结束;遇到加载失败,也要返回空结果而不是让工具调用挂住。
第二层是查看器的懒扫描。PDF UI 不再在首屏前把所有页面的注释和字段关系都扫完,而是在页面真正渲染时再做局部扫描。这样用户可以先看到第一页,后续页面继续按需加载。
这两个点合在一起,带来的不是一点性能小修,而是一种更适合 Agent 的运行方式:
用户请求显示 PDF -> 工具先返回可用的轻量结构 -> UI 快速首屏 -> 后续页面和表单信息按需加载 -> 用户操作形成可追踪的状态差异 -> Agent 只在需要时读取关键结果 这比“工具先把整份 PDF 解析成文本,再让模型解释给用户听”稳得多。
在智能运维场景里,可以把它类比成日志查询:你不会为了看一个错误窗口,先把全量日志下载到模型上下文里;你会先定位时间段、服务、trace id,再按需展开。文档 Agent 也应该这样做。
落地时可以怎么设计
如果你已经有 MCP Server,可以先从“哪些工具结果不适合纯文本”开始筛选,而不是一上来重写整套应用。
适合做成 MCP App 的工具通常有几个特征:
结果有空间结构,比如 PDF、拓扑图、流程图、地图、看板 用户需要确认证据,而不是只读一句答案 结果会被继续编辑,比如表单、批注、参数面板 数据量大,但每次只需要看一小部分 工具结果需要和后续工具调用形成闭环
一个文档工具可以按下面这种方式拆分:
display_pdf(url) -> 返回 doc_id、页数、初始 formFields、ui resource read_page_text(doc_id, page) -> 只抽取指定页文本 get_annotations(doc_id, page) -> 获取某一页批注 persist_annotations(doc_id, diff) -> 保存用户在 UI 中产生的修改 summarize_selection(doc_id, page, selection) -> 只让模型处理用户选中的局部内容 这里的重点是:display_pdf 不承担所有事情。它负责建立会话和展示界面,真正的重活拆成可增量调用的小工具。Agent 负责判断下一步需要哪个工具,而不是一次性拿走整份文件。
在 MCP Server 里,可以把 UI 资源和工具元数据绑定起来。伪代码大概是这样:
server.registerResource({
uri: "ui://pdf-viewer",
mimeType: "text/html;profile=mcp-app",
content: pdfViewerHtml,
});
server.registerTool({
name: "display_pdf",
description: "Display a PDF with page-level interaction",
inputSchema: DisplayPdfInput,
annotations: {
ui: "ui://pdf-viewer",
},
handler: async ({ url }) => {
const session = await openPdfSession(url);
const formFields = await probeFormFields(session, { incremental: true });
return {
docId: session.id,
pages: session.pageCount,
formFields,
};
},
});生产环境里不要把这段当作可以直接复制的 SDK 代码,关键是架构分工:
模型:判断任务、选择工具、解释结果 工具:获取数据、做权限校验、维护会话 UI:承载大对象、局部交互、用户确认 存储:保存差异、批注、审计记录 这样做之后,Agent 的上下文会干净很多。模型不需要记住 PDF 每一页的细节,只需要知道当前用户正在处理哪份文档、哪些字段已确认、哪些页面需要进一步分析。
边界也要提前画清楚
MCP Apps 解决的是“工具结果如何在对话里交互呈现”,不是替你解决所有安全和产品问题。
第一,Host 支持不是天然一致。MCP Apps 是 MCP 的扩展能力,不同客户端的支持程度、沙箱策略、UI 尺寸、权限提示都会有差异。面向生产环境时,要准备纯文本降级结果。
第二,iframe 沙箱不等于业务安全。PDF URL 校验、内网地址访问、鉴权 token、文件大小、下载超时、内容类型校验,都应该在工具服务端完成。不要让模型生成的参数直接决定服务端可以访问哪里。
第三,UI 里的用户操作要进入审计链路。比如删除批注、修改字段、提交表单,这些动作不能只留在前端状态里。它们至少应该有 doc_id、page、operation、user、timestamp 和变更前后摘要。
第四,增量加载不等于永不全量处理。需要全文搜索、全局摘要、合规抽取时,仍然可能需要离线索引或后台任务。区别在于,交互链路不应该被全量任务阻塞。
第五,不要把 MCP App 变成“万能小网页”。它应该服务于工具调用的证据呈现和局部操作,而不是把完整业务系统塞进聊天窗口。聊天里的 UI 越复杂,越要有清晰的状态边界和退出路径。
对智能运维 Agent 的启发
智能运维开发里有大量“半结构化证据”:告警截图、巡检 PDF、变更附件、容量报表、云账单、拓扑图、压测报告。过去我们习惯把它们变成文本,再交给模型推理。
但很多时候,文本化只是折中方案。
更好的架构应该允许 Agent 这样工作:
1. 先调用工具打开证据对象 2. 用 UI 呈现可验证的局部内容 3. 用户在界面里选择、批注或确认 4. Agent 只读取被确认的结构化结果 5. 后续诊断、总结、工单流转再进入模型 这会让 Agent 从“替用户读完所有材料”变成“带着用户处理关键证据”。对生产系统来说,后者往往更可靠。
MCP Apps 的价值就在这里:它把工具调用、交互界面和模型上下文拆开了。PDF Server 的增量加载只是一个具体例子,但这个例子已经说明,文档型 Agent 不必再把整份文件塞给模型硬扛。
真正值得借鉴的不是某个 PDF 示例本身,而是背后的工程原则:
大对象留在工具和 UI 层,模型只接收当前决策需要的最小事实。
这条原则会比任何一个单点优化都更长久。
#智能运维 #Agent开发 #MCP #MCP Apps #文档智能 #LLM应用工程
夜雨聆风