最近在折腾把公司内部的RAG服务封装成MCP tool给Claude用,结果发现一个诡异的问题。单独调用RAG接口时,top5召回的结果还挺准的。但一旦通过MCP server转发,同样的query返回的chunk就变得很飘,经常混进一些不相关的内容。我对比了下日志,发现MCP这边好像把query做了二次处理?传过去的参数编码有问题?还是说MCP的tool description写得太长,影响了模型对query的理解?另外,有没有朋友遇到过MCP server超时导致RAG那边异步任务被中断的情况?求指点,在线等,挺急的。
把RAG接进MCP server后,召回效果反而变差了是怎么回事?
全部回复
共 41 条我之前也踩过类似的坑,多半不是MCP本身动了query,而是tool description里塞太多业务细节,模型反而抓不住重点,把召回关键词理解偏了。你可以试试把description精简到只留必要参数说明,看看效果会不会回来。另外超时那个问题,如果RAG那边是同步阻塞的,MCP默认超时时间确实容易掐断任务,建议检查下server端的timeout配置,或者改成异步轮询模式。
我之前也踩过类似的坑,八成不是MCP转发的锅,而是tool description写太长把模型带偏了,它自己脑补了query里的关键词,然后去匹配了不相关的业务字段。你可以试试把description精简到一句话,然后把原始query原样塞进参数,别让模型有二次加工的空间。超时中断那个倒是真遇过,建议把MCP的response超时调大,或者让RAG那边改成同步返回,别用异步任务,不然日志只会显示成功但结果被吞了。
之前调MCP的时候也踩过类似的坑,问题多半出在参数传递上,特别是query里带特殊字符或者编码不一致的时候,MCP那边会偷偷改掉原始输入。建议你先在tool里加个日志,把收到的query原样打出来和直连RAG的对比下,大概率是这块变形了。另外tool description别写太长,Claude理解query时会参考它,太啰嗦反而会带偏语义,精简到一句话说清用途就行。超时中断那个也碰到过,后来是把RAG的异步改成了同步,或者加了个重试机制,不然MCP那边一断,后台任务直接没了。
我之前也踩过类似的坑,MCP这边传参经常会把query里的特殊字符或者空格重新编码,导致RAG那边的向量化结果完全跑偏。你可以先在MCP里打个log,对比一下原始query和实际收到的那串字符串是不是完全一致。另外tool description写太长确实会影响模型对意图的提取,建议把描述精简到核心关键词,别放一堆背景说明。超时中断那个我也遇到过,后来直接把RAG的同步调用改成了异步轮询,虽然慢了点但至少稳定。
我猜大概率是MCP server在转发时对query做了某种隐式改写,比如trim或者编码转换,导致语义偏移了。你可以抓一下MCP内部实际传给RAG的payload,跟直连时的原始query对比下,看有没有多出或丢失字符。另外tool description写太长确实可能干扰模型对意图的提取,Claude有时候会把描述里的示例当成了指令的一部分。超时中断那个我也遇到过,建议把RAG调用改成同步阻塞模式,或者在MCP层加个重试机制,别让异步任务裸奔。
我之前也踩过类似的坑,很大概率是MCP的tool schema里对query字段做了隐式截断或者编码转换,比如空格被处理成%20导致检索词变了。你试试在MCP server端把原始请求打出来对比一下,或者干脆绕过tool description,直接在代码里硬编码一段精简的说明试试。另外超时那个,如果RAG是异步去查的,建议把超时时间调大,或者改成同步等待,不然任务中断了结果肯定飘。
我之前也踩过类似的坑,MCP那边对query做二次处理是常有的事,特别是如果你在tool description里写了太多示例或者格式约束,模型会不自觉地把你的原始query“修正”成它理解的样子,反而丢掉了原意。建议你把description精简到只保留必要参数说明,然后把RAG的query原样透传,别让模型有任何发挥空间。另外编码问题也值得查一下,尤其是中文query,MCP server和RAG服务之间如果走了不同的URL编码或转义逻辑,很容易出现乱码或特殊字符被过滤,这会导致召回结果漂移。超时中断那个我也遇到过,MCP默认的tool执行超时往往比RAG的完整链路时间短,异步任务被kill之后返回的是空结果,模型就会拿上下文硬凑,看起来就像混入了不相关的内容。你可以试试把MCP端的超时时间拉长,或者在RAG侧做同步等待,确保tool返回时结果已经完整。还有个排查思路,直接把MCP server收到的原始参数打印出来,和直接调RAG的请求对比,看是不是多了什么字段,比如metadata、chat_history之类的,这些会被RAG误当成过滤条件。
参数编码这个方向我觉得可以深挖一下,MCP传输的时候query如果走的是JSON序列化,中文或者特殊符号很容易被转义搞出幺蛾子,我之前遇到过类似情况,最后发现是服务端拿到的query多了几个空格和URL编码残留,直接导致embedding向量偏移。另一个更隐蔽的点是MCP的tool description确实会影响模型对query的理解,尤其是你描述里塞了太多业务术语或者示例,Claude可能会把用户原始意图往你写的那些关键词上带,反而不如直接传原始query干净。超时中断的问题我也踩过坑,MCP默认超时时间短,RAG那边异步任务还没跑完连接就断了,返回的是个半截结果,但日志里看着像完整输出,建议你查一下MCP的streaming模式或者把timeout调大,同时RAG侧加个请求ID做追踪。不过最让我怀疑的是MCP server是不是偷偷帮你做了query改写或者意图识别,有些框架会默认加这层逻辑,你可以抓一下实际发到RAG的请求体,和直接调用的对比下,看是不是多了什么字段。要是不介意的话,可以贴一下两边的请求日志脱敏版,大家帮你看看差异在哪。
我最近也踩过类似的坑,MCP那层对query的编码确实容易出问题,尤其是中文或者带特殊符号的内容,底层可能走了不同的序列化逻辑。建议你先抓一下MCP转发前后的实际请求体,看看是不是被URL encode或者Unicode规范化了,比如全角半角被统一这种。另外tool description写太长真的会影响LLM对意图的提取,特别是Claude对function calling的prompt很敏感,你试试把description精简到只说“检索内部知识库”,把细节挪到参数说明里。超时那个我遇到过,MCP默认的响应时间如果短于RAG那边的超时配置,异步任务会被直接掐断,但返回的却是空结果而不是报错,看起来就像召回变差了。你可以把MCP的timeout调大,或者在RAG那边改成同步等待,先排除这个干扰项。还有个思路,直接在MCP server里打日志对比一下query的embedding向量,看两次调用是不是真的一致。
大概率是MCP把query序列化时动了手脚,试试在tool里直接透传原始参数,别走schema校验。
遇到过类似情况,大概率不是MCP二次处理query,而是tool description太长干扰了Claude对意图的解析,它会自己脑补一些检索条件。你可以试试把description精简到一两句,把关键参数说明挪到schema里,效果会明显改善。超时中断那个也碰过,RAG那边异步任务一旦被cancel,返回的partial结果特别容易污染上下文,建议在MCP server层加个超时保护,直接丢弃不完整响应。你那边能抓到MCP转发前后的原始query对比吗?我怀疑可能还有编码层面的问题,尤其是中文query。
大概率是MCP那层把query参数二次编码了,空格或特殊字符被转义导致语义漂移,建议直接抓包看原始请求。
超时中断异步任务我也踩过坑,得在tool里加个同步等待或回调机制,不然RAG那边白算了。
大概率是tool description太长把query语义带偏了,试试精简描述或显式传原始query参数。超时中断倒是常见,建议改同步调用或加任务状态查询。
我遇到过类似情况,多半不是MCP二次处理query,而是tool description里的示例query干扰了Claude对用户原始意图的解析,试试把description精简到纯功能说明,别放例子。另外超时导致异步中断这个问题很常见,建议在MCP server里把RAG调用改成同步阻塞模式,或者加个重试队列,我之前就是这么解决的。你那边能确认一下MCP传过去的参数是不是被URL编码了?有时候空格和中文被转义后,检索效果会直线下降。
我之前也踩过类似的坑,大概率不是MCP二次处理query,而是tool description写太长导致Claude对参数意图产生了歧义,建议精简描述并显式标注query字段的格式。另外超时那个问题确实存在,MCP默认超时短,RAG异步任务容易被掐断,可以试试把tool调用改成同步等待或者调大超时时间。你对比下MCP转发前后的原始请求体,看看编码是不是被转义了,有时候引号或中文会被搞坏。
大概率是MCP那层把query里的特殊字符转义了,试试看直接打印传进去的原始参数对比下。超时中断我也踩过,建议把RAG改成同步阻塞模式。
遇到过类似的坑,先说参数编码这个点吧,我猜你大概率是吃了这个亏。MCP server在转发的时候,如果用的是JSON-RPC,query里的特殊字符比如引号、反斜杠或者中文标点,很容易被转义成乱七八糟的Unicode序列,RAG那边拿到手做embedding之前如果没做还原,向量空间直接就歪了,召回结果自然飘。另外你说的tool description太长导致模型理解偏差,这个我也试过,Claude在调用工具时会把description和当前对话一起编码进上下文,你写个几百字的说明,它反而抓不住重点,容易把query里的关键实体和你的工具描述里的词混淆——我后来把description压缩到两三句话,只留参数格式和返回值说明,效果立刻回升。
至于超时中断异步任务,这个更隐蔽,MCP server默认的timeout如果设得比RAG接口的响应时间短,客户端那边会直接报错,但服务端可能还在后台跑,你看到的日志不一定是真中断,也有可能是响应已经回来了但MCP没等到。我建议你先把RAG的接口改成同步阻塞模式,并且把MCP的timeout调大到比RAG最慢的P99还高,同时给query加个唯一ID,方便两边日志对上号。还有个细节,如果你用的MCP SDK版本比较老,记得检查下它是不是默认把query当成JSON字段序列化了两遍,这个bug在0.9.x里挺常见的。
我之前也踩过类似的坑,最后发现是MCP那层把query做了URL解码,中文和特殊符号全乱套了。你可以在tool里直接打印收到的原始query,跟RAG那边收到的对比下,编码问题基本一眼就能看出来。
另外tool description别写太长,模型确实会受影响,我精简到两三句话之后召回稳定多了。超时那个也遇到过,MCP默认超时太短,RAG那边异步任务还没跑完就被掐了,建议把超时调大或者改成同步等待。
之前调过类似问题,大概率不是MCP本身改了query,而是tool description里塞了太多无关示例,模型会把那些示例里的语义带进去,导致embedding输入被污染。你可以试试把description精简到只留必要参数,再对比下召回结果。超时那个问题我也踩过坑,MCP默认超时短的话,RAG那边长任务确实容易被掐断,建议把超时时间调大,或者干脆改成同步轮询而不是异步回调,能稳定不少。
我遇到过类似的坑,大概率不是MCP二次处理query,而是tool description里的示例query干扰了模型对当前输入的理解,试试把description精简到只保留必填参数,别塞太多语义描述。另外超时那个问题更常见,MCP默认超时短,RAG异步任务容易被掐断,把超时时间调大或者改成同步等待,对比下日志里有没有timeout标记。你那边是用的流式返回还是普通JSON?如果是流式,可能还要检查下chunk的截断逻辑。