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 工具开发,希望这篇文章能帮你少踩几个坑。

相关推荐
FogLetter3 小时前
远程连接MCP:当AI的“手”不再受限于本地
aigc·openai·mcp
ZzT6 小时前
各大 AI 的系统提示词被扒光了,我从里面学到了写指令的功夫
ai编程·claude
武子康7 小时前
调查研究-212 智谱 ZCode Harness for GLM-5.2:国产 Coding Agent 从“模型能力“走向“工程执行环境“
大数据·人工智能·深度学习·llm·claude·glm·智谱
老程序猿9 小时前
一个撇号里,藏得下 3 个 bit——system prompt 隐写手法拆解
ai编程·claude
love530love10 小时前
WorkBuddy + 本地 ComfyUI Wan2.1 文生视频实战:从连续报错到成功出片的完整踩坑记录
人工智能·windows·python·音视频·devops·comfyui·mcp
L3S10 小时前
你的 Agent 为什么总失忆?—— Memory 设计从入门到 Claude Code
agent·claude
玉宇夕落11 小时前
mcp的学习
mcp
ServBay1 天前
不会写代码也能建站?AI 时代,非技术创始人如何从零搭建自己的 Web 项目
后端·mcp
Awu12271 天前
⚡从零开发 Agent CLI(五)实现一个可治理、可扩展的工具系统
前端·人工智能·claude