Claw Agent MCP 接入全记录:从卡死两天到手机随身调用
作者:刘磊 | Claw AI Agent 独立开发者
前言
这篇文章记录了 Claw Agent 最近两件事:一是 MCP 工具在 Claude Desktop 上无限卡死问题的完整调试过程,二是把 MCP 接入手机端的实现。中间踩了不少坑,GPT 也参与了部分诊断,一并记录下来。
一、问题描述:run_python 工具无限卡死
Claw Agent 通过 MCP(Model Context Protocol)协议接入 Claude Desktop,提供 8 个本地工具,其中 run_python 是最核心的工具之一,用于在本地 venv 环境里执行 Python 代码并返回结果。
问题出现时的现象很简单:调用 run_python 之后,工具直接卡死,没有任何输出,等到超时才返回失败。而且这个问题是间歇性的------重启 Claude Desktop 之后可能恢复,但过一会儿又开始卡。
最诡异的是,START 日志都打不出来:
python
def run_python(code: str) -> str:
print("START") # 这行都看不到
result = subprocess.run(...)
这说明问题发生在 subprocess 启动之前,而不是 Python 执行本身。
二、诊断过程:三天排查,弯路走了不少
第一阶段:怀疑 subprocess 参数
最初的方向是调整 subprocess 的各种参数:
python
# 尝试1:加 CREATE_NO_WINDOW
result = subprocess.run(
[_VENV_PY, tmp_path],
creationflags=subprocess.CREATE_NO_WINDOW,
...
)
# 尝试2:加 stdin=DEVNULL
result = subprocess.run(
[_VENV_PY, tmp_path],
stdin=subprocess.DEVNULL,
...
)
# 尝试3:改用 -c 参数直接传代码
result = subprocess.run(
[_VENV_PY, "-c", code],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.DEVNULL,
...
)
这些改动都没有从根本上解决问题。
第二阶段:找到真正的根因
在 GPT 的帮助下,梳理出了更清晰的诊断框架:
问题不在 subprocess 参数,而在 MCP worker 线程本身是否能正常调度到 subprocess。
通过一个关键实验确认了这一点------把 run_python 改成纯返回字符串:
python
@mcp.tool()
def run_python(code: str) -> str:
return "DIAGNOSTIC_OK"
结果:工具正常返回,没有卡死。
这说明 MCP worker 本身没有问题,问题出在 subprocess 启动这一层。
进一步排查发现,旧版本代码里有一个 clean_env 变量:
python
# 问题代码
clean_env = {k: v for k, v in os.environ.items()
if k not in ('USERPROFILE', 'APPDATA', 'TEMP', ...)}
subprocess.run([python, tmp_path], env=clean_env, ...)
这段代码的本意是"净化环境变量",但把 USERPROFILE、APPDATA、TEMP 这些 Windows 关键系统变量也剥掉了,导致 Python 的 import machinery 在启动时找不到必要路径,卡死约 4 分钟。
第三阶段:修复
修复方案是重写 run_python,完全去掉 clean_env,同时改用 -c 参数传递代码,并强制指定 venv 路径:
python
@mcp.tool()
def run_python(code: str) -> str:
"""执行 Python 代码,安全沙箱:subprocess + 10 秒超时"""
import subprocess
_VENV_PY = r"D:\本地模型文件夹\Claw\venv\Scripts\python.exe"
_PROJECT = r"D:\本地模型文件夹\Claw"
code = code.strip()
if not code:
return "❌ 代码为空"
try:
result = subprocess.run(
[_VENV_PY, "-c", code],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.DEVNULL, # 断开 stdin 继承,防止管道锁死
text=True,
encoding="utf-8",
errors="replace",
timeout=10,
cwd=_PROJECT,
creationflags=subprocess.CREATE_NO_WINDOW,
)
out = (result.stdout +
("\n[stderr]\n" + result.stderr if result.stderr else "")).strip()
return out if out else "(无输出)"
except subprocess.TimeoutExpired:
return "❌ 代码执行超时(10秒)"
except Exception as e:
return f"❌ 执行失败: {e}"
关键改动:
- 去掉
clean_env,不再过滤环境变量 - 用
-c参数直传代码,不再写临时文件 - 加
stdin=subprocess.DEVNULL,防止 STDIO 环境下管道锁死 - 加
CREATE_NO_WINDOW,避免 Windows 弹出控制台窗口
修复后,run_python 稳定运行,再也没有出现卡死。
三、另一个坑:async/sync 混用
修复 clean_env 的同时,还发现了另一个问题:mcp_agent_stdio.py 里所有工具函数最初都是 async def,内部用 asyncio.to_thread 包装同步操作。但 FastMCP 的 event loop 在某些情况下会被这个模式阻塞。
修复方案是把所有 7 个工具函数从 async def 改成普通的同步 def:
python
# 修复前
@mcp.tool()
async def search(query: str) -> str:
return await asyncio.to_thread(_do_search, query)
# 修复后
@mcp.tool()
def search(query: str) -> str:
return _do_search(query)
这个改动让工具执行更稳定,不再出现偶发的 event loop 阻塞。
四、手机端 MCP 接入
调试稳定之后,顺手把手机端也接进来了。
架构:
yaml
手机 claude.ai → MCP 连接器 → ngrok 隧道 → 本地 8000 端口 → mcp_server.py
mcp_server.py 是 HTTP 版本的 MCP 服务,区别于 STDIO 版本:
- STDIO 版本(
mcp_agent_stdio.py):Claude Desktop 专用,全部 8 个工具 - HTTP 版本(
mcp_server.py):通过 ngrok 隧道对外暴露,目前开放全部 9 个工具(含search、ask_claw、run_python、read_file等),ngrok 地址仅供个人使用,未对外公开分享
接入过程中遇到了几个问题:
问题1:notifications/initialized 阻塞
MCP 协议里 notifications/initialized 是一个通知消息,不需要回复。但原来的实现会让它进入 queue 等待 60 秒超时,导致每次初始化都要卡一分钟。
修复:
python
if method == "notifications/initialized":
return # 立即返回,不进 queue
问题2:ngrok 反向代理导致 base_url 错误
ngrok 转发时,request.base_url 返回的是 http://localhost:8000/,而不是外网的 https://feminize-gothic-edition.ngrok-free.dev/,导致手机客户端拿到错误的回调地址。
修复:
python
forwarded_host = request.headers.get("X-Forwarded-Host")
forwarded_proto = request.headers.get("X-Forwarded-Proto", "https")
if forwarded_host:
base_url = f"{forwarded_proto}://{forwarded_host}"
else:
base_url = str(request.base_url).rstrip("/")
接入成功后,手机上的 claude.ai 可以直接调用 Claw Agent 的搜索工具,随时随地搜索信息,体验比预期好很多。
五、经验总结
这次调试最大的教训是:不要在没有充分日志的情况下乱改参数。
clean_env 这个问题排查了两天,根本原因是没有在 subprocess 启动前后各打一条日志。如果一开始就这么做:
python
print("BEFORE_SUBPROCESS")
result = subprocess.run(...)
print("AFTER_SUBPROCESS")
第一天就能定位是 subprocess 启动问题,而不是 MCP worker 问题或者 Python 执行问题。
另外,在 Windows 上做 STDIO + subprocess 的组合,坑比预期多很多。CREATE_NO_WINDOW、stdin=DEVNULL、环境变量继承这些细节,每一个都可能是卡死的根因。
后续计划
- Cloudflare Tunnel 替换 ngrok :域名
claw-ai.eu.org申请中,审批下来后绑定 Cloudflare,解决 ngrok 频繁断线问题 - Playwright 浏览器抓取:解决 Bocha 搜索覆盖不到抖音/小红书内容的问题
- Search Fusion Layer:多源并行搜索已上线(Bocha + Bing + RSS),持续优化时效性
如果你也在做类似的 MCP 工具开发,希望这篇文章能帮你少踩几个坑。