最近正在做一个MCP智能体项目,整体进入尾声,完成了功能测试和压力测试,准备部署生产环境。客户提出一个问题,有没有比较简单直接的方法,可以监控线上MCP服务状态? 我翻了翻的mcp接口测试脚本,简化了下。如果你正好需要,供参考。
原理如下:给 HTTP 服务探活,谁都会 curl 一下;MCP 服务却一直缺这么一把"curl"——直接 curl 过去,看不出死活。它跑在有状态的 JSON-RPC 协议上:得先 initialize 拿 session,再 tools/list 看看工具在不在,最后 tools/call 真调一次才算数。少一步,拿到的都是"假绿"。
最难查的就是这类故障:Agent 一直在收 error,用户无感,但业务已经崩了。
这篇就补上 MCP 缺的这一把——245 行、只依赖 requests 的检查脚本,运维、研发、测试都能上手:
- 三层检查
(连通性 → 可用性 → 端到端),fail-fast,一次 200ms 内出结果 - 命令行 + JSON 双输出
,接进 CI 就是一个 step - 一张踩坑表
:协议版本、SSE 解析、session 丢失,坑我都替你踩过了
curl 怎么用,它就怎么用:
- 看服务在不在
: python3 mcp_monitor.py -u http://host:8080/mcp - 连后端依赖一起查
:末尾加 --full,真调一次工具验证全链路 - 每 30 秒盯一次
:末尾加 -i 30,Ctrl+C 停止 - 接进 CI / 喂日志
:加 --json,靠exit code判成败(0 健康 / 1 不健康)
最省事的一条是:
bash python3 mcp_monitor.py -u http://127.0.0.1:8080/mcp --full
输出 HEALTHY 就是三层全通,exit code 0 可以直接接流水线。
一、MCP 为什么不能直接 curl
给 HTTP 服务探活,一行就搞定:
bash curl -f http://localhost:8080/health || echo"服务挂了"
HTTP 是无状态的:发一个请求,看响应码,完事。
但 MCP 不行。它跑在 JSON-RPC 2.0 上,而且有状态:
先 initialize握手,拿服务端返回的Mcp-Session-Id后续每个请求都要带上这个 session,否则 401/403 工具能不能用,得问 tools/list工具背后的依赖(向量库、数据库、消息队列)活没活,只有真调一次才知道
所以一个能用的探针至少要覆盖这三层。端口 ping 通不算活着,那只证明 TCP 没断。
二、写之前必须知道的三个协议细节
这三个点不知道,代码一定写不对。
① 请求体固定三件套
json {
"jsonrpc":"2.0",
"method":"initialize",
"params":{"protocolVersion":"0.1.0","capabilities":{},
"clientInfo":{"name":"mcp-monitor","version":"1.0.0"}},
"id":1
}
jsonrpc 和 id 一个都不能少——少了服务端会当成非法请求。
② 响应有两种格式,必须都兼容
MCP 服务可能返回 application/json,也可能是 text/event-stream(SSE,AI Agent 服务常用)。所以请求头要同时声明两种接受格式:
python "Accept": "text/event-stream, application/json"
SSE 的响应体长这样,每条消息带 data: 前缀,解析时得先把前缀剥掉:
data: {"jsonrpc":"2.0","id":1,"result":{...}}③ session 在响应头里,不在响应体里
这是最容易踩的一个坑。Mcp-Session-Id 是 header,不是 body 字段:
python new_sid = resp.headers.get("Mcp-Session-Id") or session_id
第一版我把 session 从 body 里找,找了半小时才反应过来。
三、三层递进设计:fail-fast 是关键
┌─────────────────────────────────────────────┐
│ Layer 1: initialize(连通性) │
│ TCP 通 + 协议握手成功 → 才算活着 │
│ 失败 → 立即返回,后续检查无意义 │
└─────────────────────────────────────────────┘
↓ 通过,拿到 session_id
┌─────────────────────────────────────────────┐
│ Layer 2: tools/list(服务可用性) │
│ 能列出工具清单 → server 正常 │
│ 顺便打印工具名,方便排查"工具没注册" │
└─────────────────────────────────────────────┘
↓ 通过
┌─────────────────────────────────────────────┐
│ Layer 3: tools/call(端到端 · 可选) │
│ 真调一个最轻量的工具,验证后端依赖全链路 │
│ 需要 --full 才开(会打到后端) │
└─────────────────────────────────────────────┘这三层是严格递进的:Layer 1 挂了,session 都拿不到,再测 Layer 2 只是白等一个 timeout。所以 Layer 1 失败直接 return:
python except Exception as e:
result["checks"].append({"name": "initialize", "status": "fail", ...})
result["healthy"] = False
return result # ← 直接返回,不做后续检查
Layer 3 为什么单独开关? 因为它会真打到后端依赖。探活本身要轻——工具调用重、或有副作用,就别默认开,或者降低频率(30s / 60s 一次)。
四、核心代码:统一处理 JSON 和 SSE 两种响应
整个脚本的关键就是一个函数,把"发请求 + 解析响应 + 透传 session"包干净:
python def_rpc_call(url, method, params=None, session_id=None, timeout=10):
"""发送 JSON-RPC 2.0 请求,返回 (result_dict, elapsed_ms, session_id)"""
payload = {"jsonrpc": "2.0", "method": method,
"params": params or {}, "id": 1}
headers = {
"Content-Type": "application/json",
"Accept": "text/event-stream, application/json",
}
if session_id:
headers["Mcp-Session-Id"] = session_id # 后续请求必须带
start = time.time()
resp = requests.post(url, json=payload, headers=headers, timeout=timeout)
elapsed_ms = round((time.time() - start) * 1000, 2)
resp.raise_for_status()
new_sid = resp.headers.get("Mcp-Session-Id") or session_id
# 两种响应格式都要兼容
if"text/event-stream"in resp.headers.get("Content-Type", ""):
for line in resp.text.strip().split("\n"):
if line.startswith("data:"):
return json.loads(line[5:]), elapsed_ms, new_sid
raise ValueError("SSE 响应中未找到 data 字段")
return resp.json(), elapsed_ms, new_sid
有了它,三层检查就是调它三次、每次换个 method。耗时顺手就算出来了——延迟本身就是监控指标。
五、跑起来
5.1 单次检查
bash python3 mcp_monitor.py -u http://127.0.0.1:8080/mcp --full
==================================================
MCP 健康检查 ✅
==================================================
地址: http://127.0.0.1:8080/mcp
总延迟: 35.23 ms
状态: HEALTHY
✅ initialize (18.3 ms)
✅ tools/list (14.64 ms) tools=['memory_search']
✅ memory_search (2.29 ms)
==================================================一次 35ms,三层链路全查完。 挂掉时也一样清楚:
MCP 健康检查 ❌
状态:UNHEALTHY
❌ initialize (2000 ms) — 502 Server Error:Bad Gateway一眼就知道是网络层还是协议层的问题。
5.2 持续监控 + JSON 输出
bash # 每 30 秒完整检查一次,JSON 输出方便日志采集
python3 mcp_monitor.py -u http://127.0.0.1:8080/mcp -i 30 --full --json
5.3 接进 CI(靠 exit code)
bash python3 mcp_monitor.py -u $MCP_URL --json --full
# exit 0 = 健康,1 = 不健康
放进 .gitlab-ci.yml 就是一个 step,挂了流水线直接红。
六、踩坑表:这五个坑我都踩过
initialize 报"协议版本不兼容" | 0.1.0,不是程序版本 1.0.0 | |
data 字段 | Accepttext/event-stream, application/json | |
Mcp-Session-Id | ||
tools/call | tools/list 返回的真实名字,可能带命名空间前缀 | |
最后一条值得展开:Layer 3 超时往往不是 MCP 服务的问题,而是它身后某个依赖在打摆子。 这正是端到端检查的价值——把"MCP 挂了"和"MCP 背后的东西挂了"区分开。
七、还能往哪扩
- 加告警
:用 exit code 包一层 shell,失败就发通知(连续失败 N 次再报,避免抖动误报) - 定时跑
: crontab一行,日志落文件 - 接 Prometheus
:把 latency / status 导出成 /metrics,进 Grafana 看趋势 - 多实例巡检
:shell 循环多个地址,一屏看完整个集群
先把它跑起来,再考虑扩展。这个脚本 245 行,pip install requests 就能跑。
八、写在最后
这个探针来自一个真实的 AI Agent 项目——服务经常"Agent 报 timeout,但运维看服务是绿的"。写完之后,单次 200ms 内把三层链路查清楚:网络、协议、还是后端依赖,一眼定位。
如果你也在做 MCP / AI Agent,这套三层检查思路可以直接搬;协议不是 MCP 也没关系,"递进 + fail-fast"一样适用。
建议收藏——下次 Agent 报 timeout,照着命令敲一遍,比在群里问"服务是不是挂了"快得多。
你在 MCP 探活上踩过什么坑? 比如 session 过期、SSE 流式响应解析,评论区说说,我挑几个一起看。
后台回复「源码」,获取完整配套源码包(245 行脚本 + README + FAQ,可直接跑)。