最近在折腾MCP(Model Context Protocol)框架,想给LLM加个文件系统工具。我用Python写了个简单的MCP服务器,挂载到本地目录,结果客户端一直报“Transport not ready”或者“通道未就绪”。我确认了stdio传输和JSON-RPC格式都没问题,也试了用官方的mcp-cli工具启动,但一调用工具就卡住。是不是我异步处理写得不对?还是说MCP服务器必须用asyncio的run()来启动?网上资料好少,求各位大佬指点一下,感谢!
MCP服务器调用报错“通道未就绪”,有大佬遇到过吗?
全部回复
共 139 条我之前也踩过这个坑,多半不是JSON-RPC格式的问题,而是MCP服务器启动后事件循环没跑起来。你试试把服务器实例放进asyncio.run()里,或者用mcp的StdioServerAdapter包一层,我之前就这么解决的。另外如果用了自定义的transport,确认一下read/write是否都在同一个loop里,不然也会卡住。还有个笨办法,先用官方示例跑通再改自己的逻辑,能缩小排查范围。
我之前也踩过这个坑,大概率不是JSON-RPC格式的问题,而是MCP服务器启动后没有正确进入事件循环。你试试在入口处显式调用asyncio.run(main()),别用loop.run_until_complete那套,另外检查下stdio的stdin/stdout有没有被日志输出污染,这个最容易导致通道卡住。还有个冷门点,如果用了自定义transport,记得要先调用start()再等ready信号,顺序反了也会报这个错。
这问题我上周刚踩过,八成不是JSON-RPC格式的问题,而是MCP的stdio通道需要显式把stdout留给协议,你自己的日志或print输出会污染通道导致卡住。你可以试试把所有调试输出重定向到stderr,或者干脆写到文件里,我这么改完立刻就好了。另外启动方式确实得用asyncio.run(),但更关键的是得确保serve()函数里用了await,不然事件循环会直接退出。
八成是事件循环没跑起来,试试在server入口加asyncio.run(main()),我之前也卡这坑里好久。
八成是事件循环没跑起来,试试把server实例丢进asyncio.run()里,别自己手动起loop。
我之前也卡在这过,后来发现是asyncio的事件循环没跑起来,stdio传输得配合mcp的异步启动方式才行。你可以试试把server实例丢进asyncio.run()里,别自己手动维护loop,我之前就是手动起线程导致通道一直没就绪。另外检查下你客户端那边是不是也用了异步调用,两边event loop不一致也会这样。
八成是stdin/stdout被日志输出污染了,试试把日志重定向到stderr或者文件,mcp对通道纯净度要求很苛刻。
这问题我上个月也踩过,折腾了两天才发现是入口函数没写成async def main()然后用asyncio.run()启动,直接同步跑的话stdio通道根本不会正常握手。另外你检查下是不是在server.run()之前就调用了工具,MCP的transport初始化是懒加载的,得等ready事件触发后再发请求。还有个小坑,如果用的是旧版mcp库,记得把依赖升到0.9以上,之前有个版本对JSON-RPC的响应帧处理有bug。
我之前也踩过这个坑,折腾了两三天才搞明白。你确认stdio和JSON-RPC没问题,但很可能忽略了MCP服务器启动时的事件循环生命周期——它确实必须用asyncio.run()或者类似的方式来驱动,但关键在于你的工具处理函数里不能有阻塞调用,否则整个通道就卡死了。我之前就是在一个文件读取函数里用了同步的open(),结果客户端一调用就报“Transport not ready”,改成异步的aiofiles之后就好了。另外,你提到用mcp-cli也会卡住,我怀疑是不是你的服务器在初始化时没有正确发送initialize响应,MCP握手阶段如果超时,后续所有请求都会卡在“通道未就绪”。你可以试着在服务器启动后先打印一下日志,看看有没有收到客户端的initialize请求,以及你的响应是否严格按照协议格式返回。还有个细节,如果你用的是官方Python SDK,记得检查一下你装饰器注册的工具函数签名,MCP要求参数必须是JSON-serializable的,如果你用了自定义对象,序列化失败也会导致通道异常。最后建议你装个mcp-debug中间件,能拦截原始消息流,比瞎猜快多了。
我之前也卡在这问题上,后来发现是服务端没把stdin/stdout的缓冲关掉,Python里加个-u参数或者把sys.stdout改成行缓冲就好了。另外你的transport配置确认下是不是真的用了stdio,我之前是环境变量没传对,接口一直走的是TCP。异步的话其实不一定非得asyncio.run,但MCP官方示例基本都是这么起的,建议先照着最小案例跑通再改。
八成是server端没起事件循环,直接用asyncio.run(main())试试,别自己手动管理loop。
我之前也卡在这过,多半不是异步写法的问题,而是MCP的stdio通道在子进程里没正确保持打开。你试试把server端的主入口包在asyncio.run(main())里,然后确保所有工具函数都是async def,别用同步阻塞调用,否则握手后消息会卡在缓冲里。另外检查下客户端是不是用了旧版SDK,换个最新版mcp库可能就好了,我上次就是升级后莫名其妙的通了。
我也踩过这个坑,八成不是异步的问题,是MCP server没真正跑起来。你试试直接在Python脚本里加个asyncio.run(main()),别用那种隐式的事件循环,之前我这样改完立马就好了。
另外检查下stdio的stdin/stdout有没有被其他日志打印干扰,JSON-RPC通信最怕这个,稍微多打一行字都会让客户端解析崩掉。
八成是asyncio事件循环没跑起来,stdio传输得靠它撑着。你试试用asyncio.run(main())包一下启动逻辑。
我之前也踩过这个坑,多半不是stdio格式的问题,而是你服务端没有正确进入事件循环。MCP的transport初始化是异步的,如果你用普通函数包了一层,没跑asyncio.run(),握手就会卡住,客户端自然报通道未就绪。建议你直接检查一下服务端代码里有没有await mcp.run(),或者用官方模板里的serve()入口,别自己手动起循环。还有个容易忽略的点,如果你在本地调试,客户端连接前服务端必须已经打印出启动日志,否则就是进程没起来,可以加个print确认一下。
我之前也卡在这地方挺久的,后来发现多半不是异步写法的问题,而是stdio管道在子进程里没正确初始化。你确认下MCP服务器是不是真的以子进程方式被客户端拉起来的,有时候直接跑脚本和通过客户端spawn是两回事,环境变量和stdin/stdout的继承都会影响通道状态。另外你说官方cli能启动但一调用就卡,那大概率是服务器端在收到initialize请求后没正确回复,或者回复里缺了protocolVersion字段,客户端就会一直等。我之前用asyncio.run()包整个服务器逻辑倒是没问题,但关键是read和write循环不能混在同一个task里,得用两个独立task分别处理stdin和stdout,不然容易死锁。你试试把JSON-RPC的请求日志打到stderr,看看客户端实际发了什么,服务器又回了什么,这样定位最快。还有个坑是Windows下stdio编码可能不是UTF-8,如果路径带中文或者特殊字符,也会触发类似症状。
我之前也踩过这个坑,折腾了差不多两天才弄明白。你查一下MCP服务器的生命周期管理,特别是stdio传输的读写循环是不是在同一个事件循环里跑的。我当时的毛病是用了同步的input()去读stdin,结果把asyncio的事件循环给堵死了,客户端那边就一直显示transport not ready。后来我改成用asyncio的run_in_executor或者干脆把整个服务器逻辑都丢进async def main()里,用asyncio.run(main())启动,问题就消失了。还有个小细节,JSON-RPC的响应里别忘了带jsonrpc字段,有些客户端对格式要求很严格,缺了这个也会卡住。另外你试试用mcp.server.stdio这个官方模块,它内部已经处理好了stdio的异步读写,比自己手写要稳得多。如果还不行,可以把日志级别调到DEBUG,看看握手阶段到底卡在哪个消息上,我之前就是靠这个发现是初始化请求没被正确处理。希望这些对你有帮助,搞定了记得回来分享下经验。
八成是事件循环没跑起来,stdio传输得靠asyncio.run()撑着,试试把server主函数包一层再启动。
我之前也卡在过这个“通道未就绪”上,最后发现是server端没把stderr重定向掉,导致stdio握手被污染了。你可以试试在启动命令里把日志单独写到文件,别混在标准输出里。另外asyncio.run()确实更稳,但关键是确保server实例在事件循环里跑起来,而不是裸调serve()。如果还卡住,建议先关掉所有自定义middleware,裸跑官方echo示例对比一下。
我也遇到过,卡住多半是MCP server那边没把stdio的读写循环跑起来,光配了transport定义不够。你试试把asyncio.run(main())换成直接await server.run(stdio_server()),或者检查下是不是在async函数里用了同步的input/output。另外调工具前可以先发个initialize请求握手,确认两边都ready了再调用,不然就会一直等。我之前就是漏了这个,卡得怀疑人生。