90%开发者踩坑:MCP Resource注册≠模型能读取资源
文章目录
- 前言
- 一、先分清两个容易搞混的东西:Tool 和 Resource
- 1.1 两者底层逻辑完全反过来
- 二、只注册Resource,模型绝对看不见的根本原因
- 2.1 registerResource只是“贴告示”,不是“送货上门”
- 2.2 为什么设计成不自动全量灌入?四个现实痛点
- 三、完整四步流程,资源才能真正进到模型眼里
- 3.1 第一步:MCP Server完成资源注册
- 3.2 第二步:Client调用listResources发现全部资源
- 3.3 第三步:readResource读取资源真实文本
- 3.4 第四步:塞入SystemMessage,模型终于能看见
- 四、Resource不用ToolMessage,底层逻辑区分清楚
- 五、Resource和RAG别搞混,适用场景完全不同
- 六、收尾必做:关闭MCP Client释放进程
- 七、全文核心总结,一次记牢不踩坑
P.S. 无意间发现了一个巨牛的人工智能教程,非常通俗易懂,对AI感兴趣的朋友强烈推荐去看看,传送门https://blog.csdn.net/HHX_01
前言
做AI Agent集成MCP的时候,我前几天熬到半夜三点,差点把键盘砸了。
我吭哧吭哧写完registerResource代码,控制台打印注册成功,日志干干净净一点报错没有。转头问模型文档内容,模型两眼一抹黑,啥都不知道。
当时我内心OS:我都把资料挂服务器上了,模型是瞎了还是装失忆?合着我写的代码全是空气?
后面翻透协议、搭最小Demo跑通全链路,才搞懂核心误区:注册资源 ≠ 模型能读到资源。今天掰开揉碎讲清楚,看完再也不用通宵debug。
一、先分清两个容易搞混的东西:Tool 和 Resource
1.1 两者底层逻辑完全反过来
很多人刚上手直接把他俩当成一套东西,踩坑第一步就在这。
Tool是模型主动伸手要东西,Resource是应用硬塞给模型看,一个主动一个被动,跟外卖和快递似的。
Tool像外卖:模型饿了主动下单,Runtime接单做完把餐送回去;Resource像快递,App提前把包裹塞模型手里,模型不用开口就能直接看。
| 能力类型 | 谁掌控使用时机 | 怎么进模型上下文 |
|---|---|---|
| Tool工具 | 大模型自己判断需不需要调用 | 执行完成后包装成ToolMessage传给模型 |
| Resource资源 | 上层Host应用全权说了算 | 应用读取内容后,手动塞进SystemMessage |
举个最简单的例子:同一个MCP服务,同时挂了query_user查询工具、docs://guide文档资源。
用户说“查001号用户”,模型自己发起Tool调用;用户问“使用指南写了啥”,模型能回答全靠应用提前把文档塞进去。
二、只注册Resource,模型绝对看不见的根本原因
2.1 registerResource只是“贴告示”,不是“送货上门”
你在Server调用注册方法,本质就是在服务器门口贴一张告示:我这有一份docs://guide文档,谁想读随时来拿。
贴告示不代表把文档打印好塞模型手里啊!总不能小区门口贴个商品广告,快递自动把货送每家每户吧?那不得把小区住户全淹了。
这里有个关键逻辑:模型能读取的内容,只来自当前对话messages、绑定Tools。服务器注册资源,不会自动追加进对话消息。
2.2 为什么设计成不自动全量灌入?四个现实痛点
别吐槽协议设计反人类,自动全量塞资源才是灾难。
- Token直接爆炸:上百份文档一次性丢进去,上下文直接拉满,计费翻倍还降推理速度
- 无关内容干扰判断:用户只问用户查询,你把全套部署文档全塞进去,模型容易跑偏答非所问
- 权限管控失效:不同用户能访问的资源不一样,自动加载会泄露私密文档
- 场景匹配混乱:部分资源只在特定任务生效,全部常驻上下文完全没必要
所以资源加载权限,必须交给上层应用Host自己把控。
三、完整四步流程,资源才能真正进到模型眼里
从Server注册到模型读取,完整链路一步都不能少,缺一步直接失效。
3.1 第一步:MCP Server完成资源注册
用registerResource注册,四个核心参数缺一不可:资源名称、唯一URI、文件类型、返回内容的回调函数。
很多人把URI当成网页链接,还跑去浏览器输入打不开,然后疯狂怀疑代码出错。
这里的URI就是内部专属身份证,docs://guide、file://xxx这种格式,只给MCP客户端识别用,跟互联网网址半毛钱关系没有。
执行完这段代码,仅完成“对外声明存在这份资源”,模型这边毫无感知。
3.2 第二步:Client调用listResources发现全部资源
Host启动MultiServerMCPClient连接本地MCP子进程,调用listResources拉取所有服务的资源清单。
这一步相当于你扫小区公告栏,把所有告示全部看一遍,知道现在有哪些资源可以读取。
光看清单没用,清单只有名字和地址,没有正文,模型还是啥都看不到。
代码里需要循环遍历所有服务、服务下全部资源,记录对应的serverName和uri,这是后面读取的两个必填参数。
3.3 第三步:readResource读取资源真实文本
拿着服务名+资源URI,调用读取接口,才能拿到文档正文。
有人疑惑为啥不能只传URI?一台客户端能同时连三五台MCP服务,不同服务完全可能出现一模一样的URI,只传URI系统分不清去哪取文件,直接迷路报错。
读取返回的内容可能有多段文本,需要循环拼接,整合完整资源内容。到这一步,应用才真正拿到文档文字。
3.4 第四步:塞入SystemMessage,模型终于能看见
把拼接好的资源文本,封装成系统提示词,和用户提问HumanMessage一起组成对话数组传给大模型。
这一步就是临门一脚,前面三步全是铺垫。前面忙活半天,最后忘了塞SystemMessage,等于快递到楼下没上楼,模型照样收不到。
完整链路简化版:
registerResource → listResources发现 → readResource读正文 → 整理文本 → SystemMessage注入 → LLM读取内容
四、Resource不用ToolMessage,底层逻辑区分清楚
之前有同事问我,读取资源为啥不用ToolMessage包装,调工具却必须用?我给他打了个比方一下就懂了。
ToolMessage是模型主动下单后的回执,必须带tool_call_id,证明对应哪一次工具调用;Resource是提前备好的说明书,对话一开始就放在系统提示里,模型没发起任何调用,自然不需要回执。
两种链路可以同时存在于同一个MCP服务:
- Resource链路:Host主动读取 → SystemMessage → 模型直接读取作答
- Tool链路:模型发起调用 → Runtime执行逻辑 → ToolMessage回传结果给模型
五、Resource和RAG别搞混,适用场景完全不同
现在做Agent的朋友,一想到给模型加外部知识,不是RAG就是MCP Resource,经常纠结二选一,其实他俩根本不冲突,是互补关系。
好比你随身带一本薄薄的操作手册(Resource),同时家里有一整柜藏书(RAG向量库),薄手册随时揣兜里,厚藏书需要的时候再翻找。
| 使用场景 | 优先选择方案 |
|---|---|
| 简短、固定、每次对话都需要的说明文档 | MCP Resource直接读取注入上下文 |
| 海量文档,需要根据用户问题检索匹配片段 | RAG向量检索 |
| 需要执行查询、读写文件、调用外部接口 | MCP Tool工具调用 |
Resource不是RAG替代品,两者解决不同规模、不同加载时机的上下文需求,项目里经常搭配使用。
六、收尾必做:关闭MCP Client释放进程
之前写Demo忘记关客户端,脚本跑完控制台一直挂着,进程后台疯狂占内存,我找了半小时才发现是MCP子进程没销毁。
Node子进程启动后会持续占用stdio通信通道,不手动关闭,脚本永远不会正常退出,后台堆一堆僵尸进程。
标准稳妥写法:把关闭逻辑放进finally代码块,不管执行成功还是报错,都会自动断开连接、销毁子进程。
七、全文核心总结,一次记牢不踩坑
- registerResource仅完成资源声明,不会自动注入模型上下文;
- 资源可见完整四步:listResources发现 → readResource读取 → 整理文本 → 写入SystemMessage;
- 三层权责划分清晰:Server负责提供资源、Client负责读取资源、Host负责控制上下文注入、LLM负责解析内容;
别再幻想服务器注册完资源,模型自动全知全能。AI应用的上下文控制权,永远握在开发自己手里,偷懒省略步骤,就要通宵改bug。
P.S. 无意间发现了一个巨牛的人工智能教程,非常通俗易懂,对AI感兴趣的朋友强烈推荐去看看,传送门https://blog.csdn.net/HHX_01
