dsh-unreal-engine
把 Unreal Engine 5.8+ 的 MCP 服务接入 DeepSeek Harness —— 在 DSH 的设置里 一键探测并配置端点,装上、接上、就能用。
DSH agent ──[dsh-mcp-client]──> UE 5.8 内置 MCP server ──> 编辑器
DSH 官方自带 Epic 官方自带
↑
本插件:设置页 + 端点探测
命令里的两个占位符
本文档的命令用到两个变量,先设好再用:
| 占位符 | 含义 | 怎么设 |
|---|---|---|
$env:DSH_HOME |
DSH 的配置根目录,通常 C:\Users\<你的用户名>\.dsh |
见下 |
$RepoRoot |
本插件源码目录(从 GitHub 克隆后的仓库目录) | 见下 |
# DSH_HOME:普通 PowerShell 里它可能是空的 —— DSH 只在自己的子进程里注入它
if (-not $env:DSH_HOME) { $env:DSH_HOME = "$env:USERPROFILE\.dsh" }
# RepoRoot:把下面这行改成你克隆下来的目录
$RepoRoot = 'C:\path\to\dsh-unreal-engine'
两个变量都只对当前终端会话有效。想永久生效就写进用户环境变量。
它解决什么问题
不装本插件时,接入 UE 要手写这段 YAML(改 cordis.patch.yml):
#- insert:
# - id: mcp-unreal
# name: '@deepseek-ai/dsh-mcp-client'
# config:
# serverName: unreal
# transport: streamable-http
# url: 'http://127.0.0.1:8000/mcp'
# ...
而且端点写错、端口被占、编辑器没开时,得到的只是一个空工具列表,没有线索。
装本插件后,这些变成一个设置页:自动探测,探不到就手动粘贴日志行。
安装(建议直接让dsh帮你安装,省时省力。文档文件里面有很多dsh安装时亲自踩过的坑,已总结出文字,方便ai查阅)
本插件走 bundle 频道(和 dshmarket、dsh-chat-manager、dsh-better-sidebar 一样):
插件自己带 cordis.patch.yml 负责挂载,你的 ~/.dsh/cordis.patch.yml 保持为空。
这是卸载能安全的前提 —— 卸载不需要动用户文件。
1. 让包能被 profile 解析到
cd $env:DSH_HOME\profiles\web
pnpm add file:$RepoRoot
或者(不想让 pnpm 改 package.json 时)建一个目录连接:
New-Item -ItemType Junction `
-Path "$env:DSH_HOME\profiles\node_modules\dsh-unreal-engine" `
-Target '$RepoRoot'
⚠️ 放
profiles/node_modules(共享层),不是profiles/web/node_modules。 原因见下面「为什么放共享层」。
2. 登记到 profile 的 bundles 列表
编辑 $env:DSH_HOME\profiles\web\package.json,把包名加进 dsh.profile.bundles:
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
"...",
"dsh-unreal-engine" ← 加这一行
],
"patchReload": "live"
}
}
这一步是关键:只有出现在 bundles 里,DSH 才会应用本包自己的 cordis.patch.yml。
3. 确认 home 补丁是空的
$env:DSH_HOME\cordis.patch.yml 应该只有 []。
如果里面还有 unreal-engine / mcp-unreal 的 insert 块,删掉它们 ——
否则同一组 id 会出现两次,加载失败。
4. 校验 + 重启
dsh --profile sdk # 能安静退出(exit=0、无输出)= 插件树能加载
然后重启 DSH。
卸载
因为走的是 bundle 频道,卸载不需要手动改任何 YAML:
1. 从 bundles 列表移除
$env:DSH_HOME\profiles\web\package.json → 删掉 "dsh-unreal-engine" 那一行。
2. 移除包
cd $env:DSH_HOME\profiles\web
dsh plugin --profile web remove dsh-unreal-engine
(dsh plugin 只是把参数转发给 pnpm,所以 pnpm remove dsh-unreal-engine 等价。)
如果当初用的是 junction,直接删掉它:
Remove-Item "$env:DSH_HOME\profiles\node_modules\dsh-unreal-engine" -Force
Remove-Item "$env:DSH_HOME\profiles\web\node_modules\dsh-unreal-engine" -Force -EA SilentlyContinue
3. 清理插件数据(可选)
Remove-Item "$env:DSH_HOME\unreal-engine.json" -Force -EA SilentlyContinue
4. 重启 DSH
分发给别人
打包(你自己做)
cd $RepoRoot
npm pack --pack-destination ..\dist
产出 dsh-unreal-engine-0.1.0.tgz(约 30 KB)。把它发给对方即可。
打包前建议跑一遍:
npm test和node lib/js-expr-probe.mjs。
对方怎么装
假设对方把 .tgz 放在 D:\Downloads\:
# 1. 装包
cd $env:DSH_HOME\profiles\web
pnpm add D:\Downloads\dsh-unreal-engine-0.1.0.tgz
# 2. 登记到 bundles(这一步目前需要手动)
# 编辑 $env:DSH_HOME\profiles\web\package.json,把包名加进 dsh.profile.bundles
# 3. 重启 DSH
关于第 2 步为什么不能省:DSH 有个 reconcileProfilePlugins 函数会读安装包里的
dsh.bundle 声明并自动登记 bundles,但在 0.1.5-rc 这套已安装的版本里它从未被调用
(只有定义和导出)。所以 bundles 那一行必须手动加。
官方 CLI 的
dsh plugin --profile web add <包>会做这个登记, 但要求dsh在 PATH 上 —— 从 npx 缓存启动的安装环境通常没有。 用绝对路径可以绕过:& "$env:LOCALAPPDATA\npm-cache\_npx\<hash>\node_modules\.bin\dsh.ps1" plugin --profile web add <包>
⚠️ 对方的环境必须有 MCP SDK
本插件不打包 SDK,而是复用 DSH 自带的。探测功能需要能解析到
@modelcontextprotocol/client(新版)或 @modelcontextprotocol/sdk(旧版)。
解析器(lib/mcp-client-loader.mjs)会依次尝试:
- 插件自己所在位置(Node 常规向上查找)
$env:DSH_HOME/profiles与$env:DSH_HOME- DSH 自己的安装目录(从
process.argv[1]定位,覆盖 npx 缓存运行的情形) - 从 node 可执行文件位置向上找
node_modules
全失败时它会列出每一次尝试的具体原因,而不是甩一个 MODULE_NOT_FOUND。
所以如果对方探测报「无法解析 MCP 客户端模块」,把那段诊断贴出来就能定位。
为什么放共享层(profiles/node_modules)
profiles/web/node_modules 看起来更自然,但会出问题:
- bundle 是按 profile 解析的
- 而同一个包可能被多个 profile 引用(web / sdk / …)
- 放
web/node_modules时,用dsh --profile sdk启动会报Cannot find package 'dsh-unreal-engine'(已实测踩过)
放 profiles/node_modules 则所有 profile 都能解析到。
改端点端口
MCP 端点的 URL 用 !!js 读环境变量,缺省回落到 UE 默认端口:
url: !!js "process.env.DSH_UNREAL_MCP_URL || 'http://127.0.0.1:8000/mcp'"
所以改端口不用编辑 YAML —— 设一个环境变量即可:
$env:DSH_UNREAL_MCP_URL = 'http://127.0.0.1:8123/mcp'
dsh --profile web
⚠️ Loader 求值
!!js是同步的,作用域是new Function('ctx','expr',...), 没有import。所以绝不能写!!js "(await import('file:///...'))..."—— 会直接报错。 只写同步表达式(process.env.xxx这类)。
使用
打开 设置 → 虚幻引擎。
| 操作 | 说明 |
|---|---|
| 自动探测并保存 | 从 8000 端口起逐个尝试真正的 MCP 握手(不只是探端口),找到就保存 |
| 手动输入 | 探测失败时用。粘贴 UE Output Log 整行,或直接填端口号 |
| 结果区 | 显示 Toolset 数量、meta 工具名、协议版本 —— 都是真实握手得到的 |
手动输入能识别哪些格式
LogModelContextProtocol: Starting MCP server on port 8000 (override with -ModelContextProtocolPort=N)
LogHttpListener: Created new HttpListener on 127.0.0.1:8000
http://127.0.0.1:8000/mcp
8000
全部会收敛到 http://127.0.0.1:8000/mcp。
⚠️ 改完要重启 DSH
dsh-mcp-client 在启动时连接 MCP 服务器并注册工具,没有运行时重配接口。
保存设置后需要重启 DSH 才生效。
UE 侧准备(一次性)
- UE 编辑器 → Edit → Plugins → 搜索
ModelContextProtocol→ 启用 → 重启编辑器 - Window → Output Log,底部输入框执行:
ModelContextProtocol.StartServer - 确认日志出现
Created new HttpListener on 127.0.0.1:8000
架构
dsh-unreal-engine/
├── lib/
│ ├── index.js 宿主半:/unreal-engine/* 路由 + 配置持久化
│ ├── endpoint.mjs 端点解析(纯函数)
│ ├── probe.mjs MCP 握手探测(流式 HTTP)
│ ├── mcp-client-loader.mjs MCP SDK 解析(兼容新旧包名 + 多位置回退)
│ ├── client-bundle.test.mjs 客户端 bundle 契约测试(9 个)
│ ├── config.test.mjs 配置持久化测试(9 个)
│ ├── endpoint.test.mjs 端点解析 / 探测测试(15 个)
│ └── js-expr-probe.mjs !!js 求值作用域探针(4 项,可执行)
├── client/
│ └── client.js 浏览器半:设置页(settings.section)
├── tools/
│ └── ue5-dsh-doctor.mjs 连通性诊断脚本(5 段链路,零依赖)
├── skills/
│ └── unreal-mcp/ UE 操作规范(需手动拷到 $env:DSH_HOME\skills\)
│ ├── SKILL.md
│ └── references/
│ ├── setup.md
│ └── troubleshooting.md
├── cordis.patch.yml 本插件自己的挂载补丁(bundle 频道)
├── CHANGELOG.md 更新日志
├── MIGRATION.md junction → 标准安装的迁移手册
├── RECOVERY.md 应急恢复卡
├── LICENSE MIT(纯正文,供 GitHub 识别)
└── NOTICE.md 第三方组件归属 / UE EULA 注意 / 商标
MCP SDK 的解析为什么要单独一个文件
DSH 的 MCP 桥接对 SDK 的依赖改名过一次:
| 包名 | 入口 | |
|---|---|---|
| 旧 | @modelcontextprotocol/sdk |
/client/index.js、/client/streamableHttp.js |
| 新(v2) | @modelcontextprotocol/client |
.、/stdio(另拆出 core) |
本插件的探测功能复用同一份 SDK,但不能假设用的是哪一代、装在哪里 ——
它可能被提升到 profile 的 node_modules,也可能只存在于 DSH 自己的安装目录里
(从 npx 缓存运行的情形)。
mcp-client-loader.mjs 按「包名 × 子路径 × 解析根」逐个尝试,覆盖四个位置:
插件自身、$env:DSH_HOME、DSH 自己的安装目录(从 process.argv[1] 定位)、
node 可执行文件位置。全失败时列出每次尝试的具体原因,而不是甩一个 MODULE_NOT_FOUND。
配置为什么落在 JSON 文件而不是 settings.yaml
浏览器侧的 settings 文档在启动时读不到 —— 而 MCP 客户端恰好在启动时连接。
所以设置页把端点写进 $env:DSH_HOME/unreal-engine.json,宿主进程能直接读。
MCP 行的 url 用 !!js 读环境变量(DSH_UNREAL_MCP_URL),缺省回落到 UE 默认端口:
url: !!js "process.env.DSH_UNREAL_MCP_URL || 'http://127.0.0.1:8000/mcp'"
⚠️ 已知缺口:设置页保存的值和这一行目前不联动。 探测到非 8000 的端口时,需要手动设
DSH_UNREAL_MCP_URL。 这次的取舍是为了绕开「!!js求值是同步的、作用域里没有import」。
已知限制
| 限制 | 说明 |
|---|---|
| 改设置需重启 DSH | MCP 客户端只在启动时连接,没有运行时重配接口 |
| 设置值 ←→ mcp 行不联动 | 见上;探测到非默认端口需设环境变量 |
| bundles 需手动登记 | DSH 的 reconcileProfilePlugins 本该自动做,但当前版本从未调用它 |
| 客户端渲染未在浏览器实机验证 | 有 boot graph 证据 + 契约测试,但没在浏览器里点过 |
| 只支持 UE 5.8+ | 官方 MCP 是 5.8 引入的;5.3–5.7 需改用第三方 stdio server |
测试与诊断
cd $RepoRoot
npm test # 33 个单元测试
npm run probe # !!js 求值作用域探针(4 项)
npm run doctor # 连通性诊断(5 段链路)
单元测试覆盖:
- 端点解析 —— UE 真实日志行、URL 规范化、候选端口生成、越界拒绝
- 探测 —— 失败归类(ECONNREFUSED / 超时)、自动扫描
- 配置持久化 —— 读写、损坏容错、增量更新、临时文件不残留
- 客户端 bundle 契约 —— 注册 id 必须等于包名、顶层无 import/export、
不引用
node:内置模块、exports["./client"]指向真实文件
npm run doctor 会依次检查:
- Node 版本(需 >= 20)
- UE 端点可达性
- MCP 握手(协议版本、会话 id)
- 工具枚举 + Toolset 注册表(防止「工具数恒为 3」造成的假通过)
- DSH 配置落位(包是否安装、bundles 是否登记、home 补丁是否干净)
注意:
doctor需要 UE 已开并执行过ModelContextProtocol.StartServer。 第 5 项与 UE 无关,任何时候都能跑。
UE 操作规范(Skill)
skills/unreal-mcp/ 是给模型看的操作规范:渐进式工具发现的三步法、
风险分级、非幂等操作的重试纪律、安全边界。
⚠️ DSH 不会自动安装它 —— skill 只从固定路径扫描,且不扫
node_modules:<项目根>/.dsh/skills、<项目根>/.agents/skills、$env:DSH_HOME/skills、~/.agents/skills。手动拷过去(注意只扫一层):
Copy-Item -Recurse "$RepoRoot\skills\unreal-mcp" "$env:DSH_HOME\skills\unreal-mcp"
⚠️ 安全
UE 官方 MCP 默认只监听 loopback 且没有任何鉴权。 任何能访问该端口的人都能完全控制你的编辑器。
- 绝不要暴露到公网或局域网,不要设
0.0.0.0 - DSH 的审批门禁是唯一护栏,不要关闭
许可证
本包以 MIT 发布 —— 见 LICENSE。
不包含任何 Epic Games 或 DeepSeek 的代码。第三方组件归属、Unreal Engine EULA 相关注意事项与商标声明见 NOTICE.md。
No comments yet. Be the first to write one.