BlenderMCP 服务崩溃诊断与修复实录

一次"服务莫名崩溃 + 插件勾选状态被取消"问题的完整排查过程

日期:2026-07-02 环境:Windows 11 + Blender 5.1.2 + BlenderMCP v1.2 + Claude/Cursor MCP 客户端


一、问题现象

现象:使用 BlenderMCP 让 AI(Claude/Cursor)操作 Blender 时,MCP 服务会突然挂掉,Blender 插件面板里的"Server Running"勾选状态也被自动取消

关键澄清:Blender 软件本身没有崩溃,进程一直稳定运行。挂掉的只是 addon.py 里那个 socket server 线程。每次服务挂掉后,必须手动重新勾选插件、重连 MCP 客户端才能继续工作,严重影响 DaisySim 航空航天仿真项目的开发效率。


二、排查过程

2.1 第一反应:找日志

既然是"MCP 服务崩溃",第一反应是找崩溃日志。我把能找的地方都翻了一遍:

检查项 结果
Windows 事件查看器 Application 日志(近 7 天 1000 条) 0 条 Blender/Python/MCP 相关
Windows 事件查看器 System 日志(近 7 天 1000 条) 0 条相关
WER ReportArchive / ReportQueue(Windows 错误报告)
CrashDumps 目录 0 个 dump 文件
%TEMP%\blender_* 临时目录 5 个空目录,无日志
Blender 5.1 配置目录 只有 userpref.blend、bookmarks.txt,无 .log 文件
Blender 进程状态 PID 7996,482MB,Running,正常
MCP server 端口 9876 在线,get_scene_info 测试成功返回

结论:文件系统里不存在任何 MCP 服务崩溃日志。

2.2 为什么没有日志

打开 addon.py 源码一看,所有的错误处理都是这个模式:

python 复制代码
except Exception as e:
    print(f"Error in server loop: {str(e)}")
    traceback.print_exc()

print()traceback.print_exc() 的输出目标是 stdout 。而在 Windows 上从 GUI 启动的 Blender,stdout 只输出到 System Console 窗口 (菜单 Window > Toggle System Console),不写入任何文件。这个控制台窗口在 Blender 关闭后内容就丢失了。

所以历史崩溃的 traceback 早就随控制台输出一起消失了------这就是"查不到日志"的根本原因。

2.3 第一轮代码分析:发现 BaseException 漏捕获

既然没有历史日志,只能从代码层面找根因。逐行分析 addon.py 后发现了几个嫌疑点:

except Exception 漏掉了 BaseException 子类。Python 异常体系是:

php 复制代码
BaseException
├── SystemExit          ← 解释器退出时抛出
├── KeyboardInterrupt   ← Ctrl+C 时抛出
├── Exception           ← 所有"正常"异常的基类
└── GeneratorExit

addon.py_server_loop()_handle_client()execute_wrapper()execute_command() 全部使用 except Exception不捕获 SystemExitKeyboardInterrupt 。这些异常在线程中抛出时,线程会静默死亡 ------没有任何错误信息打印,self.running 仍然是 True,但线程实际已经不存在了。

Checkbox 被取消的机制scene.blendermcp_server_running(驱动 UI checkbox 的 BoolProperty)只在 StartServer/StopServer operator 执行时更新。线程静默死亡时不会触发任何状态更新。那 checkbox 为什么会被取消?答案是 Blender 的脚本重载机制

unregister() 函数会执行:

python 复制代码
del bpy.types.Scene.blendermcp_server_running
del bpy.types.Scene.blendermcp_auto_start_server
# ... 删除所有 Scene 属性

register() 再次执行时,这些属性被重新创建,默认值是 False

python 复制代码
bpy.types.Scene.blendermcp_server_running = bpy.props.BoolProperty(
    name="Server Running", default=False)

所以 checkbox 被取消 = unregister/register 循环被触发。但是什么触发了这个循环?此时还不清楚。

2.4 给 addon.py 打补丁:捕获真实崩溃信息

既然原版代码不写日志,那就给它加日志。我给 addon.py 打了补丁,做了 5 项改动:

  1. 添加模块级 logger :所有日志写入 blendermcp_debug.log 文件
  2. 捕获 BaseException :所有线程循环的 except Exception 改为 except BaseException
  3. 添加 watchdog timer:每 2 秒检查线程是否存活,死了就把 checkbox 自动设为 False 并记录原因
  4. except: 改为 except BaseException as e: 并记录
  5. 新增 _crash_reason 字段 保存崩溃原因

同时备份了原版到 addon.py.bak

2.5 首次抓到崩溃日志

用户重载插件后正常使用,10:10:12 首次捕获到崩溃日志:

ini 复制代码
10:10:12,741 [BMCP-ClientHandler] Received command: execute_code
10:10:12,760 [MainThread]                stop() called, running=True
10:10:12,760 [BMCP-ServerLoop]           Error accepting connection: [WinError 10038]
10:10:13,267 [MainThread]                BlenderMCP addon unregistered

关键证据:stop() 是在 [MainThread] 被调用的,而 execute_code 正是在主线程通过 bpy.app.timers 执行的。中间只隔了 19ms。

这说明不是线程静默死亡,而是 execute_code 执行的代码主动调用了 stop()

但此时还看不到 execute_code 到底执行了什么代码。于是我又加了两处记录:

  • _handle_client 收到 execute_code 时记录代码内容(前 300 字符)
  • stop() 里加 traceback.format_stack() 记录调用栈

2.6 破案:execute_code payload + stop() caller stack

用户再次重载插件后重现,10:13:31 的日志直接给出了铁证:

execute_code 执行的代码

python 复制代码
# Reset and re-import Hubble Space Telescope
bpy.ops.wm.read_factory_settings(use_empty=True)   ← 罪魁祸首
bpy.ops.import_scene.gltf(filepath=r'D:\work\logic\space\DaisySim\public\models\Hubble Space Telescope (A).glb')
...

stop() 的精确调用栈

scss 复制代码
execute_wrapper
  → execute_command
    → _execute_command_internal
      → execute_code
        → exec(code, namespace)
          → 用户代码第 2 行: bpy.ops.wm.read_factory_settings(use_empty=True)
            → Blender 内部: addon_utils.disable_all()      ← 重置时卸载所有插件
              → BlenderMCP.unregister()
                → bpy.types.blendermcp_server.stop()        ← MCP 服务被停掉

三、根因分析

3.1 完整因果链

scss 复制代码
MCP 客户端发 execute_code(含 read_factory_settings(use_empty=True))
  → Blender 执行"恢复出厂设置(空场景)"
    → 内部调 addon_utils.disable_all() 卸载所有非默认插件
      → BlenderMCP.unregister() 被触发
        → bpy.types.blendermcp_server.stop()  ← socket 关闭,MCP 服务挂掉
          → server_loop accept() 撞 None socket 报 WinError 10038
            → Scene 属性全被 del(checkbox 重置为 False)

3.2 为什么 AI 会生成这种代码

AI 客户端(Claude/Cursor)在操作 Blender 时,经常需要"清空当前场景再导入新模型"。最直观的清空方式就是 bpy.ops.wm.read_factory_settings(use_empty=True)------这在人工操作时完全没问题,但在 MCP 场景下是致命的,因为:

  1. read_factory_settings 会重置整个 Blender 到出厂状态
  2. Blender 内部会调用 addon_utils.disable_all() 卸载所有非默认插件
  3. BlenderMCP 作为第三方插件首当其冲被卸载
  4. 插件的 unregister() 会调用 server.stop() 关闭 socket
  5. MCP 连接断开,AI 客户端失去对 Blender 的控制

3.3 这不是 addon.py 的 bug

需要强调:这不是 BlenderMCP 插件的 bug,是使用方式的问题execute_code 接受任意 Python 代码并通过 exec() 执行,这在设计上就是"把 Blender 完全交给 AI 控制"。AI 生成的代码如果调用了会卸载插件本身的操作,自然会导致服务中断。

addon.py 原版的错误处理确实有改进空间(except Exception 漏捕获 BaseException、裸 except: 吞错误、不写文件日志),但这些都是次要问题------真正导致服务中断的是 read_factory_settings 这个操作本身。

3.4 WinError 10038 是良性竞态

日志里反复出现的 OSError: [WinError 10038] 在一个非套接字上尝试了一个操作 是个良性竞态:

  1. stop()self.socket = None
  2. server_loop 下一轮 accept() 撞上 None socket 报错
  3. 这个错误被 except Exception 捕获,不影响功能

看起来吓人,但只是 stop 过程中的副作用,不是崩溃原因。


四、修复方案

4.1 方案选择

方案 描述 优缺点
A. 改 AI 客户端行为 在 system prompt 里告诉 AI 禁用 read_factory_settings 简单,但依赖 AI 遵守,不可靠
B. 拦截危险操作 execute_code 里检测危险操作并拒绝执行 可靠,AI 收到错误后会自动改用安全方式
C. 保存连接重启 危险操作前保存客户端连接,操作后重启 server 复杂,hacky

选择方案 B :在 execute_code 开头加危险操作拦截。这样即使 AI 忘了规则,插件也会主动拦截,返回错误信息(包含安全替代代码),AI 收到错误后会自动改用安全方式。

4.2 实施的修复

execute_code 方法开头加入危险操作检测:

python 复制代码
# 危险操作黑名单
_DANGEROUS_OPS = (
    "bpy.ops.wm.read_factory_settings",
    "bpy.ops.wm.read_homefile",
    "addon_utils.disable_all",
    "bpy.types.blendermcp_server.stop",
    "bpy.ops.blendermcp.stop_server",
)

def execute_code(self, code):
    try:
        # 拦截会杀死 MCP 服务本身的操作
        for bad in self._DANGEROUS_OPS:
            if bad in code:
                msg = (
                    "Blocked dangerous operation '%s' - it would unload "
                    "the BlenderMCP addon and kill the MCP connection. "
                    "Use this safe alternative to clear the scene:\n"
                    "    import bpy\n"
                    "    for obj in list(bpy.data.objects):\n"
                    "        bpy.data.objects.remove(obj, do_unlink=True)\n"
                    "    for coll in [bpy.data.meshes, bpy.data.materials,\n"
                    "                  bpy.data.images, bpy.data.lights,\n"
                    "                  bpy.data.cameras, bpy.data.actions]:\n"
                    "        for item in list(coll):\n"
                    "            if item.users == 0:\n"
                    "                coll.remove(item)"
                ) % bad
                _bmcp_logger.warning("execute_code BLOCKED: %s", bad)
                return {"executed": False, "error": msg, "blocked": True}

        # 正常执行
        namespace = {"bpy": bpy}
        capture_buffer = io.StringIO()
        with redirect_stdout(capture_buffer):
            exec(code, namespace)
        return {"executed": True, "result": capture_buffer.getvalue()}
    except Exception as e:
        raise Exception(f"Code execution error: {str(e)}")

4.3 安全的清空场景代码

替代 read_factory_settings(use_empty=True) 的安全方式:

python 复制代码
import bpy

# 删除所有对象(不会触发插件重载)
for obj in list(bpy.data.objects):
    bpy.data.objects.remove(obj, do_unlink=True)

# 清理孤立数据(mesh、material、image 等无引用的)
for coll in [bpy.data.meshes, bpy.data.materials, bpy.data.images,
             bpy.data.lights, bpy.data.cameras, bpy.data.actions]:
    for item in list(coll):
        if item.users == 0:
            coll.remove(item)

效果一样(场景清空),但不会卸载插件,MCP 连接不受影响。

4.4 保留的诊断补丁

除了危险操作拦截,之前打的第一轮补丁也保留着:

  1. 文件日志 :所有日志写入 blendermcp_debug.log,以后再有崩溃能直接看文件
  2. BaseException 捕获:防止线程静默死亡
  3. watchdog timer:线程死了自动同步 checkbox 状态
  4. execute_code 内容记录:记录执行的代码(前 300 字符)
  5. stop() 调用栈记录:记录 stop 是从哪调的

这些作为保险层,万一以后有其他类型的崩溃,能快速定位。


五、经验总结

5.1 "查不到日志"本身就是一个发现

排查初期花了很多时间找日志,最后发现文件系统里根本没有日志文件 。这本身就是一个重要发现:addon.pyprint()traceback.print_exc() 输出错误,而 Windows GUI 启动的 Blender 不把 stdout 写入文件。

教训:如果一个程序依赖 stdout 输出错误信息,在 Windows GUI 环境下这些信息会随控制台窗口关闭而丢失。需要排查这类问题时,第一步应该给程序加文件日志,而不是花时间找不存在的日志文件。

5.2 "静默死亡"是第一假设,但要准备被推翻

代码分析发现 except Exception 漏捕获 BaseException,第一反应是"线程静默死亡导致 checkbox 不同步"。这个假设看起来很合理,但日志抓到后才发现真正的根因是 execute_code 主动调用 stop()

教训 :代码分析能发现潜在风险,但真实根因必须靠日志验证。不要在没有日志证据的情况下下结论。

5.3 execute_code 是双刃剑

BlenderMCP 的 execute_code 命令通过 exec() 执行任意 Python 代码,namespace 里注入了 bpy。这意味着 AI 可以做任何事情------包括杀死 MCP 服务本身。

教训:接受任意代码执行的接口必须有危险操作防护。不能假设调用方永远不做危险操作。

5.4 AI 生成代码需要场景感知

AI 不知道 read_factory_settings 在 MCP 场景下是危险的------它在训练数据里见过这个 API,知道它能清空场景,就用了。这种"语义正确但上下文危险"的代码是 AI 编程的典型陷阱。

教训 :给 AI 用的工具接口,应该在错误信息里明确告诉 AI 正确的做法。这样 AI 收到错误后会自动调整,而不是反复尝试同样的错误方式。

5.5 Windows 排查的环境约束

排查过程中遇到多个环境约束:

  • Bash 工具拦截带 # 注释的命令
  • wmic/reg 被安全策略禁用
  • PowerShell 工具异常
  • psutil 不可用

最终用 Python 调 wevtutil(Windows 事件命令行工具)+ gbk 解码绕过了这些限制。

教训 :Windows 环境下排查问题,Python 是最可靠的通用工具。subprocess.run(['wevtutil', ...]) 配合 decode('gbk', 'replace') 可以稳定查询 Windows 事件日志。


六、文件清单

文件 说明
addon.py 已打补丁的插件主文件(危险操作拦截 + 日志 + watchdog)
addon.py.bak 原版备份
blendermcp_debug.log 日志文件(插件重载后自动生成)
patch_addon.py 第一轮补丁脚本(日志 + BaseException + watchdog)
BlenderMCP_诊断报告.md 第一轮诊断报告
BlenderMCP_崩溃诊断与修复实录.md 本文(完整排查过程 + 根因 + 修复)

路径前缀:

  • 插件文件:C:\Users\Administrator\AppData\Roaming\Blender Foundation\Blender\5.1\scripts\addons\
  • 工作目录:C:\Users\Administrator\WorkBuddy\2026-07-02-09-03-18\

七、后续建议

  1. 给 MCP 客户端加 system prompt 提示 :告诉 AI 在 Blender 里清空场景用删除对象的方式,禁用 read_factory_settingsread_homefile
  2. 考虑给 BlenderMCP 上游提 issue/PR:把这个危险操作拦截的补丁贡献给上游项目,让所有用户受益
  3. 保留日志补丁blendermcp_debug.log 会持续记录所有操作,万一以后有其他崩溃能快速定位
  4. 定期清理日志:日志文件会增长,建议定期检查大小,必要时清空

本文记录的是一次真实的问题排查过程,从"查不到日志"到"抓到铁证"到"确认根因"到"实施修复",完整呈现了 BlenderMCP 服务崩溃问题的诊断与解决。希望对遇到类似问题的开发者有帮助。

相关推荐
QCC产品中心2 小时前
MiniMax Agent 接入实测:企业查询、股权穿透与 UBO 识别(附 Prompt 模板)
大数据·mcp·金融/非金融
ServBay20 小时前
7 个AI开发中真正用得上的 MCP Server,配合Claude Code食用效果更佳
后端·claude·mcp
小七-七牛开发者1 天前
Coding Agent 规则管理:CLAUDE.md、Skills、Hooks、Subagents 到底怎么选?
ai·大模型·agent·claude·token·loop·mcp·claudecode·ai coding
leeyi1 天前
MCP 工具集成:外部工具变 Eino Tool
aigc·agent·mcp
Flynt1 天前
配置Chrome DevTools MCP,我在Windows上折腾了两个晚上
ai编程·claude·mcp
Java陈序员2 天前
企业级!一个基于 Java 开发的开源 AI 应用开发平台!
spring boot·agent·mcp
Flynt2 天前
接手28万行遗留代码:我用codebase-memory-mcp把代码理解时间从3天压到2小时
ai编程·claude·mcp
ServBay3 天前
为什么说 MCP 是 2026 年开发者必须掌握的黄金协议?
后端·mcp
Momo__3 天前
MDN MCP Server——Mozilla 把 Web 文档接进 AI Agent,从此 LLM 不再瞎编 API
前端·ai编程·mcp