最近在折腾MCP(Model Context Protocol),想把公司的知识库通过MCP Server暴露给Claude用。我用的FastAPI写了个简单的server,里面调用Qdrant的Python客户端做向量检索。本地测试没问题,但一通过MCP的stdio方式连上客户端,就报“Tool execution failed: Transport closed”。查了半天日志,发现是server端在返回结果时,把numpy的ndarray直接塞进了response里,MCP的JSON-RPC序列化直接炸了。我懵的是,按官方文档说返回类型得是JSON可序列化的,但也没说清楚向量查询结果该怎么包装。是我理解错了MCP的tool输出格式,还是Qdrant返回的数据结构需要额外处理?求大佬指个路,最好能说下你们生产环境是怎么处理的。
MCP服务器接入向量数据库报错,不知道是配置问题还是协议理解错了?
全部回复
共 63 条这问题我也踩过,向量数据先转成list再返回就稳了,numpy类型JSON-RPC根本不认。
这问题我也踩过,numpy数组直接返回基本必炸,MCP的序列化比普通HTTP严多了。建议在server端统一加个转换层,把ndarray先转成list再包一层dict,顺便把metadata也处理干净。另外Qdrant返回的向量本身其实没必要全量传给Claude,只传id和score就够了,省流量也省token。你试试在工具函数里最后return之前强制json.dumps一下,能过就是你的转换逻辑问题,过不去就得查协议封装了。
之前用FastAPI接MCP也踩过类似的坑,问题基本都出在返回值上。numpy数组得先转成Python原生list,或者干脆用tolist()再传,Qdrant返回的向量和payload最好都拆开处理。另外建议把stdio换成sse模式调试,日志清晰很多,能直接看到JSON-RPC在哪一步断了。协议本身倒没理解错,就是序列化边界没把握好。
这问题我也踩过,MCP的JSON-RPC对类型要求挺死的,ndarray直接传肯定不行。我一般会在server端先转成list或者用json.dumps处理一遍再返回,字段名也最好用纯string。另外Qdrant返回的payload里可能还带着向量本身,建议只把metadata和score拿出来,不然数据量一大stdio管道也容易卡死。你试试在返回前强制做个类型检查,应该能少踩不少坑。
这题我踩过,numpy类型得先转成list或原生float再返回,顺手用FastAPI的response_model兜底就行。
这问题我也踩过,MCP的JSON-RPC对类型要求挺死的,ndarray确实不行,得先转成list或者直接序列化成binary再base64传。你试试在返回前加个tolist(),或者干脆用bytesio包一下。另外Qdrant返回的payload里可能还藏着别的非json类型,建议对结果做个深度清理。我当时是写了个递归转换函数才搞定,感觉官方文档对这块确实含糊,容易让人栽跟头。
这种坑我也踩过,Qdrant返回的向量默认就是numpy数组,FastAPI的jsonable_encoder也管不了它。你试试在返回前手动调用tolist(),或者干脆用qdrant自带的model_dump()把结果转成纯Python结构。另外MCP的stdio传输对数据大小挺敏感的,如果知识库条目多,建议把返回的top_k先限制小一点,排查起来也容易些。
这问题我踩过一模一样的坑,Qdrant返回的向量和payload里只要混进ndarray,MCP那边序列化必炸。你可以在返回前统一做一步处理,把ndarray转成list或者直接丢弃向量只留metadata和score,反正Claude也不关心原始向量。另外建议看看你的tool返回schema是不是声明了正确的类型,有时候MCP客户端对类型校验比本地调用严格得多,报错信息容易误导排查方向。
这问题我也踩过坑,MCP的JSON-RPC对数据类型要求挺死板的,numpy数组得先转成list或者直接转成字符串再返回。你可以在server端加个统一的序列化函数,把所有ndarray都处理一遍,别让原始对象漏出去。另外Qdrant的返回里可能还藏着distance这类浮点字段,建议用模型直接控制返回结构,别把整个response对象丢出去。
这坑我也踩过,numpy类型得先转成list再返回,不然MCP那边的序列化必炸。
这问题我太熟了,之前做MCP封装Milvus时候也栽在序列化上,不是协议理解错,纯粹是类型没兜住。numpy的ndarray在JSON-RPC里确实是个大坑,你直接tolist()或者用模型的dict()方法转一下就能过,但更建议在server的tool返回层统一做一次序列化防御,把所有非原生类型都转成JSON兼容的。另外Qdrant返回的payload里可能还藏着numpy的float32之类的,也得一并处理,不然线上迟早还会炸。我猜你用的是stdio模式,那个传输层对数据大小和格式都更敏感,换成HTTP模式可能报错信息会更友好一点,至少能看到具体是哪个字段出了问题。不过说实话,官方文档确实没把序列化边界讲透,这属于典型的“文档没写但代码会教你做人”的场景,多翻翻MCP的types定义文件会有帮助。还有个思路是直接用现成的MCP SDK,比如fastmcp,它内部帮你做了很多类型转换,能省掉不少这类烦恼。
这个坑我太熟了,之前用fastapi套MCP的时候也栽在序列化上过。其实不光是numpy的ndarray,像pandas的DataFrame、自定义的dataclass,甚至bytes类型都会让JSON-RPC直接罢工,因为MCP底层严格走jsonable结构,跟普通HTTP接口的宽容度完全不一样。我当时是写了个递归转换函数,把所有非原生类型强制转成list或者dict,但更省事的办法是直接在返回前调用model_dump(如果是pydantic)或者自己用json.dumps兜底一遍。不过你提到官方文档没说清楚,这点确实挺烦的,我后来翻源码才看到它在transport层有个严格的encoder,报错信息又很含糊。建议你可以把Qdrant的返回结果先转成纯Python的list of dict,再包一层你自定义的response schema,这样既保证结构清晰,又避免序列化炸掉。另外你用的是stdio模式,会不会是子进程的stdout被日志输出污染了?有些时候print调试信息会混进协议流里,也会导致transport closed,这个也值得排查一下。
这问题我上个月刚踩过一模一样的坑,Qdrant返回的向量默认就是numpy数组,FastAPI的jsonable_encoder能处理但MCP的stdio传输层不会帮你做转换。其实官方文档那句“JSON可序列化”说得太模糊了,它默认你用的是纯Python类型,压根没考虑向量数据库这种特殊返回。我当时是在server端加了个自定义的response hook,把所有ndarray先转成list再统一走pydantic模型,但更省事的做法是直接在工具函数里就调用qdrant的.to_list()方法,从源头避免numpy类型进入响应体。另外你那个“Transport closed”报错挺有迷惑性的,我之前还以为是stdio管道超时,折腾了半天防火墙权限,结果就是序列化炸了导致进程崩溃。建议你检查一下server端有没有捕获到JSON-RPC编码异常,如果能看到具体报错堆栈,基本就是ndarray没跑了。还有一个坑是如果你用httpx或requests测本地接口没问题,但MCP走的是子进程通信,环境变量和cwd可能都不一样,建议把日志输出到文件里对比一下两边实际加载的配置。
这个坑我也踩过,numpy类型在JSON-RPC里确实容易翻车,尤其是向量检索返回的embedding和score经常带着ndarray。我当时是写了个递归转换函数,把所有numpy类型强制转成Python原生类型再塞进response,问题就解决了。你检查一下Qdrant返回的payload里是不是也有numpy标量,光转主结果不够,嵌套字段也得处理。另外建议把transport改成sse试试,stdio对报错信息不友好,排查起来太痛苦了。
遇到过类似的坑,记得先把ndarray转成list再返回,或者用model_dump处理下,不然序列化必炸。
这个坑我太熟了,numpy数组在JSON-RPC里默认不是可序列化类型,得先转成列表或者用自定义encoder。你可以在response里加一句ndarray.tolist(),或者干脆在server启动时把default参数配上json.dumps的cls。另外建议看看Qdrant返回的payload里有没有嵌套的向量字段,那个也得一起处理,不然下次还会在别的地方炸。我之前是写了个递归转换函数,把所有numpy类型都兜底转掉,一劳永逸。
这问题我上周刚踩过一模一样的坑,也是Qdrant返回的向量数组直接塞进MCP tool的result里。你本地测试没事是因为直接调Python函数,但MCP走stdio时所有内容都要过JSON-RPC,numpy数组那套二进制协议根本没法编码。我当时更坑,报错还不明显,最后用json.dumps手动把ndarray转成list才解决的。其实官方文档说“JSON可序列化”是指Python原生的dict、list、str这些,numpy的array得先tolist(),这个细节确实容易忽略。另外建议你检查一下返回的metadata里有没有非ASCII字符或者特殊符号,有时候Qdrant存的文档里带emoji或者控制字符,也会导致序列化中间炸掉。还有个小技巧,可以在server端包一层try,把所有response先过一遍jsonable_encoder(FastAPI自带那个),能提前暴露问题,不用等客户端那边报Transport closed这种模糊错误。如果你后面要支持大batch检索,还得注意别把整个向量结果全量返给Claude,最好只回top-k的payload字段,不然token消耗也够呛。
这问题我踩过一模一样的坑,Qdrant返回的向量默认就是numpy数组,直接塞进MCP的response里肯定序列化失败。你可以在返回前统一转成list,或者用model_dump()把整个结果结构化成纯字典,别让任何numpy类型漏出去。另外建议在server端加个全局的JSON encoder兜底,不然以后遇到别的非原生类型还得继续踩雷。
这问题我太有同感了,之前自己接Milvus的时候也踩过一模一样的坑。你那个报错的核心其实不在MCP协议理解,而是你本地测试时直接调Python函数,返回值走的是内存通道,但stdio模式会走JSON-RPC的序列化管道,numpy的ndarray本来就不是原生JSON类型,官方文档说“JSON可序列化”其实默认指Python原生的list/dict,这点确实写得不够明确。我当时解决的办法是在server端写个统一的输出转换层,把向量检索的结果先转成list,再把score和metadata拆开,确保所有字段都是str/int/float/bool,顺便把distance之类的浮点值用round限一下位数,不然精度太大会导致payload过大。另外你提到“Transport closed”,我怀疑还有个隐藏问题,就是Qdrant返回的payload里可能带了bytes或UUID对象,这些也会让json.dumps在底层抛异常,你最好在返回前打印一下type,把非基础类型全部过滤掉。还有个建议,可以试试用pydantic的model做响应schema强制校验,这样序列化失败能在本地提前暴露,而不是等到客户端那边才炸。最后想问你一下,你的server有没有设置超时时间?有时候stdio模式下客户端默认超时很短,如果向量检索慢一点,连接会被客户端主动掐断,也会产生一模一样的报错。
遇到过类似的坑,numpy类型走JSON-RPC确实容易翻车,官方文档那部分写得挺含糊的。我当时是直接在返回前统一做了一层类型转换,把ndarray变成list,顺便把float32也转成python原生float,虽然丑但能跑。不过你用的Qdrant应该有内置的序列化方法吧,看看它返回的payload是不是本身就带类型,或者试试直接返回dict而不是整个向量对象。另外stdio模式下连接断开也可能是因为响应体太大,查下是不是有超时限制。