Claw Agent MCP 接入全记录:从卡死两天到手机随身调用

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, ...)

这段代码的本意是"净化环境变量",但把 USERPROFILEAPPDATATEMP 这些 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 个工具(含 searchask_clawrun_pythonread_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_WINDOWstdin=DEVNULL、环境变量继承这些细节,每一个都可能是卡死的根因。


后续计划

  • Cloudflare Tunnel 替换 ngrok :域名 claw-ai.eu.org 申请中,审批下来后绑定 Cloudflare,解决 ngrok 频繁断线问题
  • Playwright 浏览器抓取:解决 Bocha 搜索覆盖不到抖音/小红书内容的问题
  • Search Fusion Layer:多源并行搜索已上线(Bocha + Bing + RSS),持续优化时效性

如果你也在做类似的 MCP 工具开发,希望这篇文章能帮你少踩几个坑。

相关推荐
deepseek237 小时前
Claude Fable 5.1 深度拆解:推理强度、缓存降价,Agent 工作流成本如何省 45%
大模型·claude·ai agent
Geek-Chow8 小时前
MCP 模型上下文协议:四、概念地图 · 八个概念与五个组件
人工智能·大语言模型·mcp
Flynt18 小时前
browser-use团队新作:让编程Agent帮你剪视频,核心设计思路有点意思
开源·agent·claude
张彦峰ZYF19 小时前
MCP 从“能连工具”到“像 Web 一样部署”——无状态核心、扩展框架与企业级 Agent 基础设施的真正分水岭
人工智能·llm·agent·mcp
ServBay21 小时前
Claude 账号可能被盗刷,而你毫无察觉
api·ai编程·claude
AI程序员1 天前
MCP Apps 能直接返回 HTML,为什么还需要 A2UI?
人工智能·agent·mcp
大刚测试开发实战1 天前
用例写不完、回归跑不动?我把最磨人的活交给 TestHub,KPI 反而稳了
ai编程·测试·claude
Behavior1 天前
刚刚,Claude 5.1 发布!全球最强模型来了?
aigc·claude·vibecoding
步十人1 天前
MCP概念与实践全解析
mcp
wangruofeng1 天前
Claude Fable 5.1 发布,一个模型两种安全档,账单最多省 45%
aigc·ai编程·claude