最近在折腾MCP,想着把本地部署的Qwen2.5接进去当工具用。我用的Python写的FastMCP,模型跑在Ollama上。现在问题是,MCP客户端一调用工具,请求发到我的服务端,服务端再去访问Ollama的API就卡住,几秒后直接超时。单独用curl测Ollama是秒回的,模型也加载了。我怀疑是不是MCP的同步机制阻塞了事件循环,或者Ollama那边keep-alive设置的问题?也试过把请求改成异步,还是不行。有没有老哥碰到过类似情况?求指点一下排查方向,万分感谢。
MCP服务器连本地大模型一直超时,是配置问题还是我的姿势不对?
全部回复
共 24 条我上周刚踩过这个坑,最后发现是FastMCP默认的线程池太小,Ollama那边响应稍微慢点就把线程占满了。你试试在初始化MCP服务时把transport的线程数调大,或者直接给Ollama的请求加个长的connect timeout。另外确认下你的Ollama是不是有多个模型在跑,有时候显存不够会卡在加载上。
这题我熟,大概率是FastMCP的同步调用卡住了事件循环,试试把Ollama请求丢线程池里跑。
之前调MCP接本地模型也踩过类似的坑,后来发现是Ollama默认只接受单连接,MCP那边并发请求把队列占满了。你试试把Ollama的OLLAMA_NUM_PARALLEL设成2或者直接关掉MCP的并发,看能不能缓解。另外FastMCP的transport如果是stdio,注意下服务端有没有正确flush响应,有时候是缓冲没及时发出去导致客户端那边误判超时。
我之前也踩过类似的坑,最后发现是FastMCP默认的线程池太小,工具调用里同步去请求Ollama时把工作线程全占满了,新的请求就排队等超时。你可以先试着把Ollama的请求放到一个单独的线程池里,或者直接把FastMCP的transport换成异步模式,但注意别在async函数里用time.sleep这类阻塞调用。另外一个很隐蔽的点是Ollama的keep-alive,如果服务端每次请求都新建连接,而连接没被正确回收,也会导致句柄耗尽卡死,你可以用httpx或者requests的Session复用连接试试。对了,你MCP客户端那边设置的超时时间是多少?如果客户端那边给的窗口很短,服务端稍微慢一点就掐断,看起来也会像Ollama超时,实际是客户端先放弃了。我之前用curl测是秒回,但加到MCP里就慢,后来发现是模型初次加载后有个预热延迟,你可以在服务端启动时主动ping一次Ollama把模型拉进显存。还有个小工具,用netstat看下服务端和Ollama端口之间有没有大量TIME_WAIT的连接,有的话基本就是连接没复用。排查顺序建议先看FastMCP的日志,把Ollama请求前后的时间戳打出来,能直接定位是卡在连接建立还是请求发送。
这问题我碰到过类似的,最后发现是Ollama默认的请求并发设置太低,MCP那边多个连接挤一起就超时了。你试试把OLLAMA_NUM_PARALLEL环境变量调高一点,或者直接在Ollama服务启动时加个参数。另外检查下FastMCP服务端的超时时间是不是设得比Ollama的响应时间还短,尤其是模型冷启动慢的时候,curl秒回但实际推理可能要好几秒。
我之前也踩过类似的坑,后来发现是FastMCP默认的线程池太小,Ollama那边响应稍慢点,同步调用直接把worker占满了,后面的请求全堵着超时。你可以先试试把Ollama的keep_alive调大点,比如设成30分钟,然后看下FastMCP的日志里有没有堆积的报错。另外你异步改的是客户端还是服务端?如果是服务端,得确认一下是不是用的同一个loop,不然白折腾。
我之前也踩过这个坑,现象跟你几乎一模一样,curl秒回但MCP一调就卡死。后来发现大概率不是Ollama本身的问题,而是FastMCP默认的stdio传输和Ollama的HTTP keep-alive凑一块儿容易出幺蛾子。你可以先看下服务端那边到底是卡在发请求前还是等响应时,加个日志打在调用Ollama前后,基本就能定位。另外Ollama默认并发是1,如果你MCP那边有多个工具调用排队,或者上一个连接没释放,很容易把后面全堵死,可以试试调OLLAMA_NUM_PARALLEL。还有一点,别只看超时时间,把MCP客户端的超时调大一点排除误判,有时候其实只是首token慢。异步改法要注意别在async函数里混用同步的requests,那样反而更糟。我当时最后是把Ollama请求单独抽出来用httpx异步跑,再配合keep-alive关掉,问题就没了。
Ollama默认keep-alive是5分钟,但MCP那边如果每次新建连接可能会等它冷启动,试试设OLLAMA_KEEP_ALIVE=-1常驻看看。
我之前也踩过这个坑,最后发现是FastMCP默认把工具调用跑在同步线程里,Ollama那边等流式响应又慢,事件循环直接被堵死了。你可以先看看是不是在tool函数里用了requests这种同步库,换成httpx异步请求试试。另外Ollama的keep-alive有时候会抽风,建议在客户端显式设一下timeout和连接复用。还有个偏方是把模型先warmup一下再挂MCP,冷启动那几秒特别容易触发超时。
看看Ollama那边并发请求数限制,MCP可能同时发了多个请求把队列堵死了。
我之前也踩过这个坑,大概率是Ollama默认只监听127.0.0.1,而你的MCP服务如果跑在容器或WSL里,访问宿主机得换成host.docker.internal或者局域网IP,curl能通不代表服务端能通。另外FastMCP里如果你用的是同步requests去调Ollama,确实会把事件循环卡死,换成httpx的AsyncClient试试。还有个容易忽略的点,Ollama的keep_alive默认5分钟,但首次加载模型那几秒如果超过你MCP客户端的超时阈值也会直接断,先把超时调到30秒以上排除掉这个变量。
我之前也踩过这个坑,最后发现是Ollama默认的keep-alive只有5分钟,模型被卸载后第一次请求要重新加载,MCP那边超时设太短就直接断了。你可以试试把keep_alive设成-1或者调大点,再确认下MCP客户端的timeout参数是不是默认值太小。另外FastMCP如果用的是同步路由,确实容易把Ollama的流式响应堵死,换成纯异步加httpx应该会好很多。
我之前也踩过这个坑,大概率是FastMCP默认用stdio传输时把整个事件循环给堵死了,Ollama的流式响应回来没法及时处理。你可以先试试把Ollama的keep_alive调大点,或者干脆在工具函数里用httpx.AsyncClient显式发请求,别用requests。另外确认下MCP服务端是不是跑在同步的asyncio.run里,那样套异步也白搭。
我也遇到过类似情况,后来发现是FastMCP里工具函数用了同步的requests去调Ollama,整个事件循环被卡死了。改成httpx.AsyncClient之后就好了,你确认下是不是有同步阻塞调用藏在某个依赖里。另外Ollama默认keep-alive时间挺短的,模型冷启动那几秒很容易超时,可以试试调高OLLAMA_KEEP_ALIVE环境变量。
这个超时大概率不在Ollama那边,你curl秒回说明模型和服务本身没问题,瓶颈更可能出在MCP服务端处理请求的那段逻辑上。FastMCP如果底层用的是同步的stdio或HTTP handler,再往里塞一个requests.post去调Ollama,事件循环确实容易被堵死,尤其Ollama首次生成时会有个预热和排队过程,几秒超时太正常了。建议你先在服务端把调Ollama那步单独打日志,看看到底是卡在发请求前还是等响应时,这样能快速定位。另外Ollama默认的keep-alive和并发数也值得看一眼,OLLAMA_NUM_PARALLEL设得太小,多个工具调用挤在一起就会互相等。还有个小坑是MCP客户端那边的超时时间可能比Ollama生成时间短,你把它调大再试一次,有时候真不是代码问题。我之前用类似架构时,把调用改成httpx.AsyncClient加显式timeout,再确认服务端是async def,问题就没了。你可以先跑个最小复现,只留一个工具、一句prompt,排除掉并发干扰再逐步加回去。
你是不是 Ollama 那边没设 keep_alive,模型每次被调用都重新加载?
你curl的是Ollama的/v1/chat/completions还是/api/generate?MCP那边调工具时走的endpoint可能不一样。我之前也遇到过,最后发现是FastMCP默认超时设太短了,工具函数里稍微多绕一层就炸。建议先把超时调到30秒以上试试,如果还卡就抓一下Ollama那边的日志看请求到底有没有进去。
这个坑我也踩过,当时折腾了整整一个周末才搞明白。你怀疑事件循环阻塞这个方向是对的,FastMCP默认跑在asyncio里,而Ollama的Python客户端如果你用的是requests那种同步库,确实会把整个loop卡死,表现就是请求发出去但回调一直不触发,最后超时。换成httpx的AsyncClient试试,或者干脆用ollama官方库的异步接口,别混着用。另外有个容易忽略的点,Ollama默认keep-alive是5分钟,但如果你发请求时带了stream=false且模型刚好在冷启动,首次推理可能要十几秒,MCP客户端那边超时阈值如果设得短就直接断了。建议先把MCP客户端的超时调大到60秒,然后单独写个脚本模拟MCP服务端去调Ollama,看看到底是哪一段卡住。还有检查一下你是不是在MCP工具函数里做了同步的数据库查询或者文件IO,那些也会偷偷阻塞。我当时最后发现是日志写文件用了同步open,改成aiofiles就通了,你可以顺着这个思路排查下。
这个思路不错,收藏了。
我之前也遇到过类似问题,后来换了方案。