最近在折腾把公司内部的RAG服务封装成MCP tool给Claude用,结果发现一个诡异的问题。单独调用RAG接口时,top5召回的结果还挺准的。但一旦通过MCP server转发,同样的query返回的chunk就变得很飘,经常混进一些不相关的内容。我对比了下日志,发现MCP这边好像把query做了二次处理?传过去的参数编码有问题?还是说MCP的tool description写得太长,影响了模型对query的理解?另外,有没有朋友遇到过MCP server超时导致RAG那边异步任务被中断的情况?求指点,在线等,挺急的。
把RAG接进MCP server后,召回效果反而变差了是怎么回事?
全部回复
共 61 条之前踩过类似的坑,重点查一下MCP server里tool的input schema定义,如果query字段类型或描述写得不明确,模型会自己脑补加料,导致传过去的参数和你预期的不一样。另外超时中断那个大概率是同步调用方式的问题,试试把RAG改成异步返回,或者在MCP侧调大timeout,别让Claude那边等太久。
我之前也踩过类似的坑,多半不是MCP二次处理query,而是tool description里塞了太多业务术语,Claude会自己脑补出一套“意图理解”,反而把原始query带偏了。你试试把description精简到纯功能说明,另外确认下MCP那边传参是不是用了HTTP的form编码而不是JSON,chunk里混进乱码就说明编码炸了。超时那个确实会,MCP默认超时短的话,RAG异步没跑完就被掐了,建议把MCP的timeout调大或者改同步调用,先排除这两个变量再排查其他的。
我之前也踩过类似的坑,重点查下MCP server里有没有对query做额外的拼接或改写,有些框架会默认加system prompt,把原始语义带偏了。另外tool description别写太满,Claude对超长描述反而会抓不住重点,精简到一两句核心功能试试。超时那个问题,建议把RAG调用改成同步等待模式,或者在MCP里加大timeout配置,不然异步任务被kill了确实会返回脏数据。
大概率是MCP把query序列化时动了手脚,试试打印下实际收到的参数,比对下编码前后差异。
超时中断倒是常见,把RAG的异步任务改成同步重试机制能缓解不少。
这个现象我太熟了,之前折腾过类似的东西,最后定位到是MCP的tool description里塞了太多示例导致Claude把query语义带偏了。你试试把description精简成纯函数式说明,别在里头写什么“用于检索公司内部知识库文档”这种带主观色彩的引导词。另外参数编码确实是个坑,MCP的JSON序列化有时候会把中文引号或者特殊符号转义成unicode,RAG那边分词器处理不了就全乱了。超时导致异步中断我也碰到过,如果RAG是流式返回或者要跑rerank,建议在server端把超时调大,同时让MCP这边改成同步等待但内部做重试机制。还有个小技巧,你可以把query原样打日志对比一下两边收到的字符串,如果字节数都不一样那肯定是传输层出问题了。最后别忽略MCP的采样buffer,如果Claude在调用tool前先自己判断了query意图,它可能会对原句做改写,这个在日志里能看到,但很多人没注意到。
我上周也踩过类似的坑,最后发现是MCP的tool description里塞了太多示例和参数说明,Claude在构造query时反而被带偏了,把原始问题拆得七零八落,建议你试试精简description,只保留必要的检索意图。至于参数编码,我这边遇到过特殊字符被转义后召回率暴跌的情况,你可以打印一下MCP实际收到的请求体,对比和直连时的差异。超时中断那个我倒是没碰到,但如果RAG是异步的,建议把MCP的timeout调大,或者在server端改成同步等待结果返回,省得两边状态对不上。
你日志里看到的“二次处理”大概率是MCP协议序列化时把query当普通字符串拼进prompt模板了,转义或者截断都可能让语义漂掉。我之前也踩过类似坑,建议先把MCP server收到的原始query打出来跟直连RAG时的做个diff,别只看最终结果。另外tool description太长确实会抢模型的注意力,试试压缩到一两句话描述功能就好。超时那个问题更麻烦,MCP断了RAG那边不一定能感知,最好加个request id做幂等或者取消信号的透传。
你提到的query二次处理这点很关键,我之前也踩过类似的坑。MCP server在转发前如果对参数做了JSON schema校验或者字段映射,很容易把原始query里的特殊符号、空格甚至大小写给改了,检索那边拿到的东西就已经不是你以为的那个query了。建议先把MCP实际发出的request body完整打出来,跟直连RAG时的参数做逐字节对比,大概率能看出问题。至于超时中断,MCP那边默认超时一般偏短,RAG要是走异步召回确实容易被掐掉,调大timeout或者改成同步短链路试试。
我之前也踩过这个坑,后来发现八成是tool description写太长把query带偏了,模型会拿描述里的词去重写查询。建议先把description砍到一句话,只留功能说明,参数交给schema约束。另外MCP超时确实会掐断下游异步,我们改成先返回task id再轮询才稳。你可以在MCP层把原始query和改写后的query都打出来对比一下,一眼就能看出是不是二次处理的问题。
你提到的二次处理问题我也踩过,后来发现是MCP tool的input schema里query字段被加了默认的prompt template,相当于模型又改写了一遍才发给RAG,试试把schema简化成纯string透传。tool description太长确实会干扰,我压到两行以内召回就稳了。超时那个建议在MCP侧设个略小于RAG的超时阈值,不然异步任务被掐断很难排查。
你提到的二次处理挺关键的,我之前也踩过类似的坑,MCP那边对query做了normalize或者截断,导致语义直接变了。建议先把MCP server收到的原始query和RAG直接调用的query打日志对比一下,大概率是编码或转义的问题。tool description太长确实会稀释模型对query的注意力,可以精简下描述试试。超时那个我也遇到过,异步任务被掐断后返回的是半截结果,看着就很不相关。
你提到tool description太长影响query理解,这个我深有同感。之前我把description写到快两千字,结果模型经常自己脑补参数,把query拼得乱七八糟。后来精简到三句话,召回就稳多了。编码问题也值得查一下,MCP转发时如果没做URL编码,中文和特殊符号很容易被截断。超时那个我遇到过,RAG异步任务被掐断后返回的是空结果,但MCP这边会当成正常召回塞给模型,看起来就像“飘”。
先抓包看MCP转发时query有没有被改,八成是编码或转义搞的鬼。超时那个也遇到过,把RAG改同步返回就稳了。
这个思路不错,收藏了。
MCP那层估计对query做了归一化或者截断,先抓下转发前后的原始参数对比看看,八成是编码问题。
先打印MCP实际透传的query原文,八成是编码或转义把参数搞脏了。
你对比日志发现query被二次处理这点挺关键的,我之前也踩过类似的坑,MCP那边如果对参数做了转义或者拼了额外的上下文,RAG拿到的embedding就跟原始query对不上了,召回肯定飘。建议先把MCP实际传给RAG的原始query打出来跟裸调对比一下,大概率能定位到问题。至于超时中断,MCP server默认超时一般偏短,RAG异步任务被打断后返回的可能是半截结果或者兜底内容,也会让chunk看起来不相关。
我也踩过这个坑,后来发现大概率不是RAG本身的问题,而是MCP tool的description太长,把query的语义带偏了。建议先把description砍到一两句话,再固定参数结构,别让中间层再拼接改写query。另外超时中断确实常见,异步任务记得做幂等和续跑,不然后面查日志会很痛苦。
你查下MCP那边是不是默认开了什么query rewrite或者prompt template,我之前也踩过这个坑,工具描述一长模型就自己加戏,把原始query给改了。另外RAG接口最好直接透传原始参数,别让中间层做归一化处理,编码问题反而少见。超时那个我也遇到过,MCP默认timeout偏短,异步任务刚发出去就被掐了,把timeout调大点试试。
你这个情况我碰到过,八成是MCP那边对query做了截断或者转义,把原始语义搞歪了。建议先把MCP server收到的query和直接调RAG的query打出来逐字对比,大概率能看出问题。tool description太长确实会干扰模型选参数,但一般不会导致召回飘,更像是编码或拼接出错。超时那个也真实存在,MCP默认超时短的话,RAG异步还没跑完就被掐了,返回的可能是空结果或者上一轮的缓存。