项目: omni-mcp — 基于 Model Context Protocol 的多模态统一工具服务器
测试周期: 2026-02-17 ~ 2026-02-18
测试环境: Windows 10 / Python 3.12 / VS Code + Copilot Agent
工具总数: 48 项
最终通过率: 48 / 48 (100%)
omni-mcp 是一个基于 MCP (Model Context Protocol) 协议实现的多模态工具服务器,旨在为 LLM Agent 提供对文件系统、办公文档、图像处理、3D 建模、视频编辑、科学计算等领域的统一操作能力。服务器以单个 Python 文件 (omni_mcp.py, ~1400 行) 实现全部 48 个工具,通过 stdio 模式与 VS Code 中的 Copilot Agent 通信。
本报告完整记录了 48 项工具的逐项测试过程,重点覆盖以下内容:
- 每类工具的基本功能验证与业务场景测试
- 测试过程中暴露的 11 个缺陷及其根因分析与修复方案
- 涉及 MCP SDK 层面的 stdin 管道继承问题(对应 MCP Python SDK issue #671)
- GIMP 3.0、MATLAB、FFmpeg 等第三方工具在 Windows 中文环境下的兼容性适配
- 多阶段回归测试的执行过程与最终结果
测试以"智能工厂月报"为业务主线,每个工具围绕同一主题产出实际数据,最终形成包含 PPTX / DOCX / XLSX / PDF / PNG / SVG / MP4 / GIF / .blend / .FCStd / STEP / GDScript 等 16 种格式在内的完整交付包。
| 组件 | 版本 / 路径 |
|---|---|
| 操作系统 | Windows 10 x64, 16 GB RAM |
| Python | 3.12.10 |
| MCP SDK | mcp (Python), stdio 模式 |
| Blender | 4.x (D:\Blender), 另配 blender-mcp 插件 |
| FreeCAD | Python 3.11.13, PartDesign workbench, freecad-mcp 插件 (RPC localhost:9875) |
| Godot | 4.6.1 stable (D:\Godot_v4.6.1-stable_win64.exe) |
| MATLAB | 已安装,中文 Windows 默认 GBK 编码输出 |
| FFmpeg | 已加入 PATH |
| GIMP | 3.0.8 (D:\GIMP 3\bin\gimp-console-3.0.exe) |
| Inkscape | 1.4.2 (D:\Inkscape) |
每项工具的测试遵循以下流程:
- 首次调用:使用最小可用参数调用工具,观察返回结构是否符合预期。
- 功能验证:以"智能工厂"主题构造真实业务数据,验证工具的主要功能路径。
- 产物校验:对生成文件进行大小、格式、内容的基本检查(如用
xlsx_read回读写入的表格数据)。 - 异常处理:对首次失败的工具进行根因分析、代码修复,再执行回归测试直到通过。
- 难度递增:对 MATLAB、img_composite、chart_subplot 等工具追加高复杂度用例以验证工具在更大参数规模下的稳定性。
全部测试由 Copilot Agent 在 VS Code 中通过 MCP 协议自动驱动,运行日志见 logs/run_log.md。
涵盖工具: pptx_create / pptx_read / pptx_edit / docx_create / docx_read / docx_replace / xlsx_create / xlsx_read / xlsx_write / xlsx_chart
Office 文档模块整体表现稳定,所有工具首次调用即通过。
- PPTX: 创建了包含封面页、生产数据表和品质分析图表的 7 页幻灯片(45.4 KB),读取和编辑功能正常。
- DOCX: 创建了 9 章节的智能工厂月报文档(39.7 KB),包含标题、段落、多行表格,文本替换功能通过。
- XLSX: 这里踩了一个坑——初期使用
data+start_cell参数写入数据,但发现工具实际需要writes参数,格式为[{"range":"A1:G18","values":[...]}]。后续采用"先创建空骨架再逐表写入"的策略,成功构建了包含 KPI Dashboard、产线明细、设备管理、品质分析、能源环境共 5 个工作表的完整工作簿(14.8 KB)。xlsx_chart嵌入柱状图功能正常。
涵盖工具: pdf_create / pdf_read / pdf_merge / pdf_split / pdf_watermark / pdf_to_images
PDF 模块暴露了两个需要修复的代码缺陷:
pdf_read 使用 PyMuPDF 打开文档后,在 doc.close() 之后仍尝试调用 len(doc) 获取总页数。PyMuPDF 1.27.1 不允许访问已关闭的文档对象,抛出异常。
修复: 在调用 doc.close() 前将页数保存到局部变量 total = len(doc),此后使用 total 替代。
传入多个相对路径的 PDF 时,路径解析失败导致所有源文件打开为空,最终尝试保存零页文档时报错。
修复: 对 files 列表中的每个路径调用 R(f) 解析为绝对路径,并增加空列表校验和零页兜底检查。
pdf_create的content参数必须传入 JSON 数组格式(如[{"type":"title","text":"..."},...]),传入字典会被静默忽略。pdf_watermark需要显式传入rotation=0,传入rotation=45时水印不可见(疑似 ReportLab canvas 坐标系问题)。- 中文渲染方面,初始版本出现方块乱码,后将字体切换为 Microsoft YaHei (
msyh.ttc) 并实施字体注册级联(msyh → simhei → simsun → Helvetica)解决。
涵盖工具: img_create / img_info / img_process / img_convert / img_composite
基础图像操作(创建、信息读取、格式转换)均一次通过。
img_composite 参数格式: 首次调用时传入了 Python 列表对象,工具实际要求 JSON 路径字符串数组。第三次尝试使用 ["path1.png","path2.png"] 格式后通过。
后续对该工具追加了高复杂度用例:先通过 6 次并行 img_process 调用对不同素材图片执行 resize、彩色边框叠加、文字标注、圆角裁切、灰度/对比度/模糊/锐化等组合操作,生成 6 张 648×368 的统一规格瓦片,再通过 img_composite 的 grid 模式按 2×3 布局拼合,间距 12px,深色背景 (#1a1a2e)。最终产出 1968×748 的合成图(1028.1 KB),相比初始版本的 4.2 KB 提升约 245 倍。
涵盖工具: blender_exec / blender_scene / blender_render / freecad_create / freecad_exec / godot_exec / godot_run / godot_export
omni-mcp 自身的 blender_exec 和 blender_scene 首次测试超时(300s)。原因是 omni-mcp 通过命令行静默启动 Blender 进行脚本执行,冷启动耗时远超预期。
验证思路:项目中已有 blender-mcp 插件(uvx blender-mcp)直连 Blender addon 的 RPC 通道,通过该通道调用 get_scene_info、execute_blender_code、get_viewport_screenshot 均正常返回,证明 Blender 本身功能完好。随后通过 blender-mcp 创建了包含 23 个对象(地板、传送带、10 个支腿、5 个产品、机械臂、灯光、相机)的工厂场景,保存 .blend 并导出 .fbx。blender_render 使用 EEVEE 引擎渲染 1280×720 PNG(552.7 KB)。
freecad_create 和 freecad_exec 通过 omni-mcp 均正常工作。为进一步验证能力边界,通过 freecad-mcp 插件(RPC localhost:9875)实时控制 FreeCAD 完成了一个法兰轴承座的完整建模:160×100×12mm 底板(R8 圆角)、R35 外圆柱体、R25 中心通孔、R30 台阶孔、4×M10 沉头螺栓孔、侧向润滑油孔、4 个三角形加强筋。最终导出 FCStd / STEP / STL 三种工业标准格式。
godot_exec 的主要修复是将 --script 参数改为 -s,并对无 extends 声明的脚本自动包装 extends SceneTree 和 quit() 调用。godot_run 增加了 TimeoutExpired 容错处理。
godot_export 需要预先安装 Godot 4.6.1 的导出模板(1.19 GB),安装后导出正常。此外,作为综合性压力测试,从零构建了一个完整的俯视角 2D Roguelite 射击游戏——14 个 GDScript 文件、9 个场景文件,实现了 WASD+鼠标射击、3 类敌人(近战/远程/冲刺)、波次递增系统、三选一升级系统、XP/HP 掉落、暂停菜单和最高分存档等功能。导出过程中遇到 Godot 4.6 对 := 类型推断语法的严格化检查,修正 2 处后以 0 error 成功导出 Windows EXE(99.6 MB)。
涵盖工具: svg_create / inkscape_convert / inkscape_exec
svg_create 和 inkscape_convert(SVG→PNG)首次通过。inkscape_exec 首次调用超时(120s),根因与 run_python 相同,属于 MCP SDK stdin 管道继承问题(详见 4.10 节),添加 stdin=subprocess.DEVNULL 后修复。
涵盖工具: chart_create / chart_subplot
chart_create 生成日产量柱状图(47.7 KB)。chart_subplot 构建了 2×2 四面板仪表板(柱状图、折线图、饼图、簇状柱图),输出 137.0 KB。值得注意的是,chart_subplot 的饼图和面积图支持是在测试过程中补充实现的——原始代码仅支持 bar / line / scatter 三种类型,需要在 omni_mcp.py 中增加 pie 和 area 的分支逻辑。另外该工具在 MCP 协议层传输大参数时容易触发超时,最终通过在终端直接执行 Python 脚本绕过了这一限制。
涵盖工具: matlab_eval / matlab_exec
matlab_eval 用于表达式求值,首次通过。matlab_exec 首次调用时抛出 "NoneType has no len()" 异常,经排查涉及三个叠加问题:
MATLAB 在中文 Windows 下的 stdout/stderr 输出为 GBK 编码,而 _run() 函数以 UTF-8 解码,解码失败导致 subprocess.run 返回 stderr=None,后续 len(stderr) 抛出 NoneType 异常。
修复: subprocess.run 添加 errors="replace" 参数容错非 UTF-8 字符,并对 stdout/stderr 统一添加 or "" 空值保护。
生成的临时脚本文件名为 _matlab_run.m,以下划线开头,不是合法的 MATLAB 标识符,run() 函数拒绝执行。
修复: 将文件名改为 omnirun.m。
修复后,初始测试用例为简单正弦波绘图(27.7 KB)。为验证工具在复杂脚本下的鲁棒性,后续追加了 6 子图工程仪表板用例:3D 热场分布(meshgrid + peaks + surf)、FFT 频谱分析(50+120 Hz 混合信号 + findpeaks 峰值标注)、ODE45 Van der Pol 振子相空间图(5 组初始条件)、双峰直方图(histogram + makedist 高斯拟合)、8×8 传感器相关矩阵热力图(imagesc + 数值标注)、极坐标雷达图(6 维工厂 KPI),以 200 DPI 输出(576.6 KB)。
涵盖工具: ffmpeg_exec / ffmpeg_info / ffmpeg_convert / ffmpeg_clip / ffmpeg_screenshot / ffmpeg_gif
测试素材从 samplelib.com 下载了一段 H.264 1080p 30fps 的 MP4 视频(5.76s, 2781.5 KB),以此为输入完成全部 6 项测试。
ffmpeg_info 通过 ffprobe 获取视频元信息并解析为 JSON。由于 _run() 函数中 errors="replace" 会将非 UTF-8 字节替换为 Unicode 替换字符 \ufffd,这些字符混入 ffprobe 的 JSON 输出后导致 json.loads 失败。
修复: ffmpeg_info 不再经由 _run() 调用 ffprobe,而是直接使用 subprocess.run,再通过正则表达式 [\x00-\x08\x0b\x0c\x0e-\x1f\ufffd] 清洗输出中的控制字符和替换字符后解析。
其余 5 项工具测试结果:
| 工具 | 操作 | 产物 | 大小 |
|---|---|---|---|
| ffmpeg_convert | MP4→AVI | test_video.avi | 3316.7 KB |
| ffmpeg_clip | 剪辑前 2 秒 | clip_0_2s.mp4 | 33 KB |
| ffmpeg_screenshot | 第 1 秒截图 | shot_1s.png | 2962.5 KB |
| ffmpeg_gif | 前 3 秒转 GIF | preview_from_download.gif | 2098.9 KB |
| ffmpeg_exec | 自定义低码率转码 640×360 | lowbitrate_360p.mp4 | 224.5 KB |
涵盖工具: gimp_exec / gimp_python
这是调试成本最高的模块,前后编写了 14 个独立测试脚本(test_gimp.py ~ test_gimp11.py)逐步缩小问题范围。两个工具首次均超时(120s),经分析存在三个叠加的根因:
omni-mcp 调用的是 gimp-3.0.exe(GUI 版本),启动后弹出图形界面并阻塞等待用户操作。应当使用 gimp-console-3.0.exe(无头模式)。
GIMP 2.x 默认使用 Script-Fu 作为 batch 解释器,而 3.0 移除了这一默认值。必须显式传入 --batch-interpreter plug-in-script-fu-eval 参数,否则 GIMP 无法识别 -b 参数后的脚本内容,进入静默等待状态。
在 stdin 管道模式(-b -)下,将 Script-Fu 脚本包裹在 (begin ...) 形式中会导致 GIMP 无限期挂起。去除包裹,直接传递脚本内容并追加 (gimp-quit 0) 即可正常退出。
修复后的调用方式:
cmd = [GIMP, "-i", "--batch-interpreter", "plug-in-script-fu-eval", "-b", "-"]
script_input = f'{script}\n(gimp-quit 0)\n'
r = subprocess.run(cmd, input=script_input, ...)gimp_python(Python-Fu)的实现方式是将 Python 代码通过 Script-Fu 的 python-fu-eval 包裹传递,复用上述修复方案。
已知限制: GIMP 3.0 的文件保存 API 签名发生变更,file-png-save / gimp-file-save 等函数在 Script-Fu 批处理模式下静默失败(返回码为 0 但不产生文件)。脚本执行本身正常,文件 I/O 层存在兼容性问题尚未完全解决。
涵盖工具: file_write / file_read / file_copy / file_move / file_delete / file_list / file_open / run_cmd / run_python / system_info
文件读写、复制、移动、删除、打开等操作均正常。system_info 返回操作系统和已安装工具链信息。以下三项需要修复:
run_python 首次调用超时(60s),表现与 inkscape_exec 完全一致。
根因定位到 MCP Python SDK issue #671:omni-mcp 以 stdio 模式运行时,MCP SDK 通过 stdin/stdout 与 VS Code 通信。当 omni-mcp 内部通过 subprocess.run 启动子进程时,子进程默认继承父进程的 stdin 管道。在 Windows 上,子进程如果未显式关闭继承的 stdin,会持有该管道的读取端,导致 subprocess.run 永远无法收到 EOF 信号而无限期阻塞。
修复: 在 _run() 公共函数中添加 stdin=subprocess.DEVNULL,将子进程 stdin 重定向到空设备,切断管道继承链。
r = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout,
shell=shell, cwd=cwd or str(WD), creationflags=CF,
errors="replace", stdin=subprocess.DEVNULL)需要注意 GIMP 的调用使用了 input= 参数显式向 stdin 写入脚本内容,此时 subprocess.run 内部会创建独立管道,不受此 bug 影响。
该修复同时解决了 inkscape_exec 和 run_cmd 的超时问题。
传入相对路径(如 outputs)时返回了 Python 临时工作目录下的内容。原因是使用 Path(dir) 直接构造路径,未经过工作区根目录解析。改为 R(dir) 后修复。
所有命令在固定临时目录下执行,用户无法指定工作目录。添加可选 cwd: str = "" 参数并使用 R(cwd) 解析后修复。
测试过程中共发现 11 个缺陷,全部完成修复并通过回归测试。按影响面和严重程度排列如下:
| 编号 | 影响工具 | 现象 | 根因 | 修复方案 |
|---|---|---|---|---|
| D-01 | run_python, inkscape_exec, run_cmd | 子进程超时 (60-120s) | MCP SDK stdio 模式下子进程继承 stdin 管道 (issue #671) | _run() 添加 stdin=subprocess.DEVNULL |
| D-02 | gimp_exec, gimp_python | 超时 (120s) | 使用 GUI 版 gimp-3.0.exe | 改用 gimp-console-3.0.exe |
| D-03 | gimp_exec, gimp_python | 超时 (120s) | GIMP 3.0 移除默认 batch 解释器 | 添加 --batch-interpreter plug-in-script-fu-eval |
| D-04 | gimp_exec, gimp_python | stdin 模式挂起 | (begin ...) 包裹在管道模式下不工作 |
去除包裹,直接传递脚本 |
| D-05 | matlab_exec | NoneType has no len() | 中文 Windows GBK 输出致 UTF-8 解码失败 | errors="replace" + or "" 空值保护 |
| D-06 | matlab_exec | MATLAB 拒绝执行 | 脚本文件名 _matlab_run.m 非法标识符 |
改为 omnirun.m |
| D-07 | ffmpeg_info | JSON 解析失败 | errors="replace" 产生的 \ufffd 污染 JSON |
绕过 _run(),直接 subprocess + 正则清洗 |
| D-08 | pdf_read | document closed | doc.close() 后访问 len(doc) |
提前保存 total = len(doc) |
| D-09 | pdf_merge | cannot save with zero pages | 相对路径解析失败致源文件为空 | 逐文件 R(f) 路径解析 + 空值校验 |
| D-10 | file_list | 返回错误目录 | Path(dir) 未经工作区目录解析 |
改为 R(dir) |
| D-11 | run_cmd | 无法指定工作目录 | 缺少 cwd 参数 | 添加 cwd 参数 + R(cwd) |
| 文件 | 大小 | 说明 |
|---|---|---|
| outputs/smart_factory_report.pptx | 45.4 KB | 7 页幻灯片 |
| outputs/smart_factory_report.docx | 39.7 KB | 9 章节月报 |
| outputs/production_data.xlsx | 14.8 KB | 5 工作表 |
| 文件 | 大小 | 说明 |
|---|---|---|
| outputs/monthly_report.pdf | 200.9 KB | 中文月报 (Microsoft YaHei) |
| outputs/appendix.pdf | 97.0 KB | 附录 |
| outputs/merged_report.pdf | 2.6 KB | 合并文档 |
| outputs/watermarked_report.pdf | 201.3 KB | 带水印 |
| outputs/pdf_split/page_1.pdf | 1.7 KB | 拆分页 |
| outputs/pdf_images/page_1.png | 42.6 KB | PDF 转图片 |
| 文件 | 大小 | 说明 |
|---|---|---|
| outputs/composited_banner.png | 1028.1 KB | 2×3 网格合成图 (1968×748) |
| outputs/factory_banner.png | 2.8 KB | 基础 banner |
| outputs/factory_banner.jpg | 8.2 KB | PNG→JPEG 转换 |
| 文件 | 大小 | 说明 |
|---|---|---|
| outputs/production_chart.png | 47.2 KB | 日产量柱状图 |
| outputs/dashboard_subplot.png | 137.0 KB | 四面板仪表板 |
| outputs/matlab_dashboard.png | 576.6 KB | 6 子图工程仪表板 (200 DPI) |
| outputs/matlab_plot.m | — | MATLAB 完整脚本 |
| 文件 | 大小 | 说明 |
|---|---|---|
| outputs/factory_layout.svg | 95 B | 产线布局 |
| outputs/factory_layout_ink.png | 22.8 KB | Inkscape 转换产物 |
| 文件 | 大小 | 说明 |
|---|---|---|
| inputs/download_test.mp4 | 2781.5 KB | 测试源视频 (H.264 1080p) |
| outputs/test_video.avi | 3316.7 KB | MP4→AVI |
| outputs/clip_0_2s.mp4 | 33 KB | 前 2 秒剪辑 |
| outputs/shot_1s.png | 2962.5 KB | 第 1 秒截图 |
| outputs/preview_from_download.gif | 2098.9 KB | GIF (480px, 10fps) |
| outputs/lowbitrate_360p.mp4 | 224.5 KB | 低码率转码 |
| 文件 | 大小 | 说明 |
|---|---|---|
| outputs/factory_scene.blend | 0.11 MB | Blender 工厂场景 |
| outputs/factory_scene.fbx | 0.1 MB | FBX 导出 |
| outputs/factory_scene_render.png | 552.7 KB | EEVEE 渲染 1280×720 |
| outputs/flanged_bearing_housing.FCStd | — | FreeCAD 参数化零件 |
| outputs/flanged_bearing_housing.step | — | STEP 导出 |
| 文件 | 大小 | 说明 |
|---|---|---|
| outputs/godot_game_pro/ (25 files) | — | 14 GDScript + 9 场景 + 2 配置 |
| outputs/godot_game_pro/build/RogueliteShooter.zip | 34.4 MB | Windows 可执行文件(ZIP 压缩) |
| 工具 | 要点 |
|---|---|
| pdf_create | content 必须为 JSON 数组格式 |
| pdf_watermark | 必须显式传 rotation=0 |
| img_composite | images 参数为路径字符串数组 |
| xlsx_create / xlsx_write | 使用 writes 参数: [{"range":"A1:G5","values":[...]}] |
| godot_exec | 必须传 project_path;脚本含 extends 时不自动包装 |
| godot_export | 需预装 Godot 导出模板 |
| chart_subplot | 大参数易触发 MCP 协议超时 |
| gimp_exec | GIMP 3.0 文件保存 API 在批处理模式下可能静默失败 |
本测试对 omni-mcp 全部 48 项工具进行了逐一验证,覆盖 Office 文档、PDF、图像、3D 建模、矢量图形、数据图表、MATLAB 科学计算、视频编辑、GIMP 图像脚本、系统文件操作共 10 个功能模块。
测试过程中共定位并修复了 11 个缺陷。其中影响面最广的是 MCP Python SDK 在 Windows stdio 模式下的 stdin 管道继承问题(D-01),该问题波及所有通过 subprocess.run 调用外部程序的工具。GIMP 3.0 的适配(D-02 至 D-04)调试成本最高,需要同时处理可执行文件选择、解释器声明和脚本传递方式三个层面的变更。MATLAB 在中文 Windows 环境下的编码问题(D-05, D-06)和 FFmpeg 的 JSON 输出污染问题(D-07)则暴露了跨平台字符编码处理中需要关注的场景。
所有缺陷修复后经回归测试验证,最终通过率 100%。产出物覆盖 PPTX、DOCX、XLSX、PDF、PNG、JPEG、SVG、MP4、AVI、GIF、.blend、.fbx、.FCStd、STEP、GDScript、EXE 共 16 种文件格式,累计约 120 MB。
最后更新: 2026-02-18
详细运行日志: logs/run_log.md