最近在尝试把公司内部的一个文档助手用 MCP 协议包装成服务,部署到 K8s 集群上。本地用 Python 的 mcp 客户端测试没问题,但一上生产,客户端(用的官方 SDK)就频繁报连接超时,偶尔能连上也很快断掉。我检查了服务端日志,发现它明明在监听端口,也没有明显报错。怀疑是不是 MCP 的 heartbeat 机制在生产环境网络波动下不够稳定,或者我的 ServerCapabilities 配置有问题?有没有大佬遇到过类似情况,求指点一下排查思路或者调优经验。
MCP 服务部署到生产环境后,客户端连接老是超时怎么办?
全部回复
共 134 条大概率不是heartbeat的事,先查下K8s的service和ingress超时配置,特别是长连接空闲超时。
服务端监听正常但客户端断连,十有八九是负载均衡层把空闲连接回收了,调大keepalive试试。
大概率是K8s的service会话保持问题,试试开下ingress的proxy-read-timeout,或者把MCP的heartbeat间隔调大点。
大概率不是heartbeat的事,先查下K8s的service和ingress超时配置,默认往往很短。
之前我也栽这坑里,把存活探针和读超时调大点,顺便看下TCP keepalive。
我之前也踩过类似的坑,K8s里部署MCP服务超时,十有八九不是SDK或heartbeat的锅,而是网络层的问题。你检查下Service和Pod的配置,特别是sessionAffinity和负载均衡策略,MCP这种长连接服务如果被转发到不同Pod,客户端重连就会特别频繁,表现就是时而超时时而秒断。另外,生产环境里ingress-nginx默认的proxy-read-timeout通常只有60秒,MCP的流式响应很容易超,你得在注解里把proxy-read-timeout和proxy-send-timeout都调大,比如调到300s以上。还有,你的客户端SDK版本和生产环境是不是一致?官方SDK有时候版本差异会导致连接池复用逻辑变化,我遇到过0.3.x和0.4.x对TCP keep-alive的处理完全不同。先抓包看看服务端有没有收到FIN包,如果收到了但日志没报错,那大概率是空闲连接被中间设备断开,这时候需要你在ServerCapabilities里明确声明支持ping,并且客户端主动发heartbeat的频率要比网络设备空闲超时时间短。最后,你本地测试没问题不代表生产正常,K8s的service DNS解析和Pod IP变化也可能让连接池里的旧地址失效,建议用headless service试试。
我之前在K8s里跑gRPC服务也踩过类似的坑,倒不一定是MCP heartbeat的问题,先查下Service和Pod的networkPolicy是不是限制了长连接。生产环境一般有ingress或负载均衡,如果它们配了空闲超时,比如nginx默认60秒,而MCP的heartbeat间隔比这长,连接就会被中间层掐断,客户端自然觉得超时。你可以抓包看看,或者直接在Pod里用tcpdump看有没有收到对端的FIN包,这样能快速定位是网络设备干的还是应用层断的。
另外ServerCapabilities配置我建议你核对一下,特别是session相关的字段,有些SDK版本对心跳协商很敏感,如果服务端没正确声明支持或频率不匹配,客户端会主动断开。还有个思路是别用官方SDK默认的transport,试试把心跳间隔显式调短,比如改成15秒,再配合TCP keepalive的探活参数,很多云环境默认会回收空闲连接。
我遇到过类似情况,最后发现是K8s的conntrack表满了,导致新建连接被drop,但已有连接偶尔能通,很迷惑人。你可以看下集群节点的dmesg有没有nf_conntrack的报错,或者直接看服务端连接数是不是在持续增长但活跃数很低。如果服务端日志没报错,建议在客户端把超时时间拉长到30秒以上,先排除是代码逻辑还是网络层的问题,再逐步调优。
我之前搞过类似的,也是MCP包一层丢K8s,本地稳如老狗,上生产就超时。你查服务端日志没报错,但监听正常,这太典型了——大概率不是MCP逻辑问题,而是网络链路和负载均衡那层。你想想,K8s里Service如果没配好session affinity,或者Ingress的idle timeout比MCP的heartbeat间隔短,连接就会被中间层掐断,客户端那边自然觉得是超时。我那次最后发现是云厂商的LB默认超时60秒,而我的MCP心跳可能刚好卡在边缘,一波动就断。你先看看生产环境里客户端到服务端中间有几层代理,每一层的timeout都拉长到比如300秒以上,特别是TCP层和HTTP层的keepalive设置。另外,ServerCapabilities里如果声明了streaming或者有轮询类操作,客户端可能预期更频繁的消息,网络抖动时更容易触发重连逻辑。你可以把MCP的日志级别调到DEBUG,看客户端断连时服务端有没有收到FIN包,还是直接RST,这能区分是优雅关闭还是被墙。还有一个坑:官方SDK有些版本对heartbeat的容忍度很死,如果你服务端处理请求偶尔超过几秒,就可能导致客户端误判,可以试试在客户端把心跳超时参数调松一点。最后,如果你用的是WebSocket传输,记得检查K8s的Ingress是否支持WebSocket升级,有些默认配置会直接砍掉长连接。你先从这些方向抓包看看,多半能找到线索。
大概率是K8s的网络策略或负载均衡层空闲连接回收搞的鬼,先查下TCP keepalive和Ingress超时配置。
遇到过一模一样的坑,最后发现根本不是MCP心跳的问题,是K8s里Service和Pod的生命周期没对齐。你本地没问题是因为直连端口,生产环境走Service抽象层,客户端那边拿到的是虚拟IP,一旦Pod滚动更新或者健康检查探针误判,连接就断了,表现就是你说的“偶尔能连上很快断掉”。建议先抓包看下TCP层是FIN还是RST,如果是RST,大概率是后端主动关了连接,查下Ingress或者Service的idle timeout,默认值可能只有几十秒。
另外ServerCapabilities配置一般不会导致超时,最多是能力协商失败,但官方SDK对未知字段很宽容。你重点排查下K8s的readinessProbe,如果它调用的接口恰好和MCP的endpoint冲突,或者探针间隔太长,Pod状态是Ready但实际连接已经僵死,客户端就会一直等响应直到超时。还有个容易忽略的点是MCP的transport层,如果是Streamable HTTP,生产环境千万别用默认的keep-alive设置,Nginx或云LB会回收空闲连接,客户端那边必须显式处理重连,SDK版本不同行为差异很大。
建议你直接在Pod里开个临时端口用nc模拟客户端连一下,排除网络策略问题。再就是看下服务端有没有打印“connection reset by peer”或“EOF”这类日志,有的话基本锁定是中间层断连。调优的话,把MCP的heartbeat间隔调短到5秒内,同时客户端侧加个指数退避重试,能缓解但治标不治本。核心还是得让Service的targetPort和Pod端口完全一致,别用ClusterIP默认的随机端口映射,有些CNI插件对长连接支持有bug。
说实话我第一反应不是heartbeat的问题,你本地通生产断这种经典现象,大概率是K8s网络层在搞鬼。服务端日志没报错但客户端超时,先查一下Service和Pod的连通性,特别是如果走的是ClusterIP,客户端在集群外访问的话,NodePort或Ingress的会话保持和负载均衡策略很容易把长连接掐断。MCP的heartbeat机制虽然存在,但官方SDK默认超时时间其实挺长的,除非你显式改过ServerCapabilities里的pingInterval,否则生产网络波动不至于这么快就断。建议你先抓包看看TCP层有没有RST,或者用tcpdump在Pod里直接监听端口,确认是连接根本没建立还是建了之后被重置。另外K8s的service mesh(比如Istio)如果注入了sidecar,默认的idle timeout可能只有几分钟,这跟MCP长连接的需求冲突很大,我遇到过类似情况,最后是在DestinationRule里调大了connectionTimeout和idleTimeout才解决。还有一点,你检查下客户端SDK版本,有些旧版对代理或NAT穿透支持不好,如果生产环境有严格的出口防火墙,试试把heartbeat间隔调短到15秒左右,看能不能保活。配置层面我倒是觉得ServerCapabilities不太可能引起超时,顶多影响能力协商,但你可以把日志级别调到DEBUG,看服务端有没有收到客户端的initialize请求,如果连这个都没有,那问题肯定不在你的业务代码里。
我之前也踩过类似的坑,本地通和生产不通大概率不是MCP配置问题,而是K8s网络层的事。你先查下Service和Pod的存活探针是不是把MCP端口也包进去了,或者Ingress的idle timeout比heartbeat间隔还短,连接被网关掐了。另外生产环境如果有sidecar或者网络策略,可能对长连接不友好,试试在客户端把重试和keepalive调一下,比直接怀疑heartbeat靠谱。
生产环境超时断连,服务端日志没报错的话,八成是负载均衡那层搞的鬼,比如k8s的service转发模式或者云厂商的LB空闲超时设置。你可以抓包看看是SYN被丢还是RST,或者直接在客户端把连接超时和读超时分别调大测一下。ServerCapabilities一般不会导致这问题,先别纠结那玩意。
会不会是DNS解析的问题?生产环境里客户端拿到的服务地址如果解析到多个IP,有些节点网络不通就容易超时。我之前用headless service就遇到过这情况,后来改成ClusterIP加会话亲和性就好了。你也可以试试直接用Pod IP连一下,排除掉服务发现那层的问题,顺便看看是不是TCP keepalive在内核层没被启用。
超时和断连这种,先别急着怀疑heartbeat,大概率是网络策略或者防火墙对长连接不友好。你可以在服务
我个人觉得先别急着怀疑heartbeat机制,K8s里部署MCP服务超时,大概率是网络层的问题。你本地通,生产不通,最典型的差异就是Service和Pod的网络路径——检查一下是不是用了ClusterIP但客户端跨节点访问,或者NodePort的会话保持没配置好。另外,MCP的SDK默认连接超时设置可能很短,生产环境网络延迟一高就触发,你可以先调大客户端的timeout参数试试,比如从默认的5秒改到30秒。
还有个容易忽略的点:K8s的Service如果没配好readinessProbe,Pod可能还在启动中就被加入Endpoints,客户端连上去自然不稳定。你服务端日志没报错,但可能连接根本没建立成功——用tcpdump在Pod里抓包看看SYN包有没有回ACK,这能直接定位是丢包还是服务端没响应。
至于ServerCapabilities,我印象里它不影响底层TCP连接,主要是功能协商,除非你声明了某些stream能力但没实现,导致SDK走错了协议分支。建议先抓个客户端侧的详细日志,看超时是发生在连接建立阶段还是request/response阶段——这两个排查方向完全不同。我之前遇到过类似问题,最后发现是K8s的conntrack表被占满,老连接被丢弃,新连接也受影响,清理一下或者调大net.netfilter.nf_conntrack_max就解决了。
你服务端日志显示在监听但客户端连不上,这个现象我第一反应是 K8s 的 Service 或者 Ingress 层出了问题,而不是 MCP 协议本身的 heartbeat 不够稳。本地能通、生产断,最典型的坑就是 Pod 起来了但 Readiness Probe 还没过,Service 的 Endpoints 是空的,客户端请求打到 ClusterIP 上自然超时。你可以先 kubectl get endpoints 看看后端有没有真正挂上,顺便确认下 Service 的 targetPort 和容器实际监听端口对不对得上。
另外 MCP 官方 SDK 默认走的是 SSE 或者 streamable HTTP,这两种长连接对中间代理特别敏感。如果前面挂了 Nginx 或者云厂商的 LB,idle timeout 经常默认 60 秒,连接看着建上了但一空闲就被掐,表现就是偶尔能连、很快断。建议把代理的 read timeout 和 buffering 调一下,SSE 场景下 proxy_buffering 最好关掉,不然事件流会被缓存住导致客户端一直等不到数据。
ServerCapabilities 配置一般不会直接导致连接超时,那个更多影响的是握手后的能力协商,真配错了通常报的是协议层错误而不是 timeout。我倾向于先把网络链路和探针这块排干净,再回头怀疑 heartbeat 逻辑,不然容易在协议层白折腾。
K8s里是不是没配readiness探针,导致流量打到还没就绪的pod上?先查查Service的endpoints和网络策略吧。
先查下 K8s 的 Service 是不是没配 sessionAffinity,MCP 长连接被轮询到不同 Pod 肯定断。