内容
# unreagent
[](https://github.com/Getty/unreagent/actions/workflows/ci.yml)
A small **launcher / orchestrator for Unreal Engine projects**: it starts and
supervises the Unreal Editor **and** an agent (e.g. Claude Code), and gives the
agent an **MCP server** to drive the editor, trigger builds, read logs, and run
Python/Node code in prepared runtimes.
A single `.exe` — **cross-compilable from Linux to Windows**. Stdlib Go only,
plus one vendored YAML parser; `vendor/` is checked in, so builds are
offline-reproducible.
## Architecture
```
+----------------------- unreagent.exe -----------------------+
| Supervisor MCP server (HTTP :8765) |
| * ue (Job Object) <--- status / logs |
| * agent (Job Object) ue_start / stop / restart |
| * restart policy + backoff run_command (compile, ...) |
| * ring-buffer logs run_python / run_node |
| | approve (permissions) |
| v ^ |
| UnrealEditor.exe | |
| +- in-editor MCP plugin | |
| (e.g. UE LLM Toolkit, | |
| HTTP :3000) | |
| ^ | |
| Node bridge (stdio MCP) | |
| ^ | |
| Claude Code ----------+--------- unreagent-MCP |
| +----------> in-editor MCP |
| (both via --mcp-config) |
+------------------------------------------------------------+
```
Two MCP servers serve the agent:
- **unreagent-MCP** (this launcher, HTTP) — *process* layer: start / stop /
restart the editor, compile, logs, run_python / run_node, permissions.
- **In-editor MCP** (e.g. [UE LLM Toolkit](https://github.com/ColtonWilley/ue-llm-toolkit),
running *inside* the engine) — *content* layer: blueprints, assets, levels,
UE Python. Setup: [In-editor MCP setup](#in-editor-mcp-ue-llm-toolkit-setup-windows).
The launcher hands the agent both via `--mcp-config` (`mcp.extraServers` in
the config), so the agent does not have to wire anything up itself. Stdio
bridges are fully checked before the agent starts, with each step logged
loudly, instead of letting the agent run into a meaningless `-32000`
connect error: binary on PATH? bridge script present? `node_modules` there
(or run `npm install`)? does the bridge answer a real `initialize` handshake
(smoke test)? And once the in-editor server (`UNREAL_MCP_URL`) is reachable,
that's logged too — or a warning with the usual suspects if it isn't.
The same config can additionally be written to a file for **external clients**
(your own Claude session, Cursor, VS Code). Via `mcp.writeConfig` (a list of
`{path, format}`, format `mcp_json` or `vscode`) or the flag
`-write-mcp-config <path>`. Handy with `-no-agent`: the launcher writes the
`.mcp.json` and keeps UE + MCP running, you connect externally.
## Building
Requirement: Go (>= 1.19). Dependencies are vendored (`vendor/`); builds run
offline.
```bash
make windows # -> dist/unreagent.exe (cross-compile from Linux)
make linux # -> dist/unreagent (for local tests)
make all
```
Or directly:
```bash
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o dist/unreagent.exe ./cmd/launcher
```
## Windows: metadata & "Unknown Publisher"
The `.exe` carries an icon + **version info** (CompanyName, ProductName,
description, copyright) and an `asInvoker` manifest (no UAC elevation
prompt). This fills `Properties -> Details` and the program name in dialogs.
The "Unknown Publisher" warning itself comes from the **signature**, not
from metadata. Two paths:
**A) Self-signed (in the repo, for your own team / known users)** — the
`.exe` is signed with a self-signed certificate from "conflict.industries
digital GmbH". Users import the public certificate once:
```powershell
powershell -ExecutionPolicy Bypass -File signing\import-cert.ps1 # as admin
```
After that: no publisher warning, the GmbH appears as publisher. Details
and trade-offs: [`signing/`](signing/). (The private key is **not** in the
repo.)
**B) CA certificate (for a wider audience)** — an OV / EV code-signing
cert does not need user-side import (EV gives immediate SmartScreen
reputation). With such a cert you also sign the `.exe` **from Linux**
(`osslsigncode`).
Change publisher / version: `cmd/launcher/versioninfo.json` -> `make resource`.
## Try it now (demo)
The release zip ships a ready-to-go demo `unreagent.yaml` that runs **without
UE and without an agent** (`ping` as a placeholder "editor"). Just unpack the
zip and start `unreagent.exe` — the MCP server will then run on
`http://127.0.0.1:8765/mcp`. Details: [`example/`](example/). In the console:
`status`, `logs`, `c hello`, `r ue`, `q`.
> The "LLM thing" (Claude Code) is not included — it is a CLI you install
> separately. The demo therefore leaves the agent off; to arm it, set
> `agent.enabled: true` (see below).
## Setup
The launcher is **dropped into the UE project** and the config is **committed**:
1. Put `unreagent.exe` (from the release) next to the `.uproject` — gitignored.
2. Rename `unreagent.example.yaml` -> **`unreagent.yaml`**, place it next to
the `.uproject` and **commit it to the repo**. This file is portable.
3. Start `unreagent.exe` (double-click or console). Alternative config path
via `-config <path>`.
`unreagent.yaml` contains **no machine-specific paths**. Editor, `compile` and
`package` are auto-derived. A minimal config is enough:
```yaml
agent: { enabled: true, command: claude, claudeIntegration: true }
permissions: { enabled: true, mode: allow_all, deny: ["Bash(rm -rf *)"] }
runtimes: { python: { enabled: true }, node: { enabled: true } }
mcp: { enabled: true }
```
### Portability: paths are resolved at runtime
| Placeholder | Resolution |
|---|---|
| `${ENGINE}` | env `UE_ROOT` -> `engineRoot` (local.yaml) -> auto-detect of the Epic default paths |
| `${PROJECT}` / `${PROJECT_DIR}` / `${PROJECT_NAME}` | `.uproject` next to the config (auto-detect) |
Machine-specific overrides go in a **git-ignored `unreagent.local.yaml`**,
which is overlaid on top of `unreagent.yaml`:
```yaml
# unreagent.local.yaml (DO NOT commit)
engineRoot: "D:/UE/UE_5.7" # if auto-detect fails
agent: { command: "C:/.../claude.cmd" } # if claude is not on PATH
```
Recommended `.gitignore` entry in the UE project:
```
unreagent.exe
unreagent.local.yaml
```
### When the agent ends (`/quit` or crash)
In window mode the agent is the lead process — if it ends (e.g. `/quit`) and
is not restarted, the launcher prompts on the console (`agent.onExit: ask`,
the default):
```
Agent ended.
[Enter] shut down everything (UE + launcher)
[k] keep editor running, drop to launcher console
[r] restart the agent
> _ (30s -> shut down everything)
```
Without a TTY (headless `-p`), it shuts down cleanly instead. Alternatives:
`onExit: shutdown` (always shut everything down immediately) or `onExit: leave`
(everything keeps running, warning only — manual Ctrl-C required).
## In-editor MCP: UE LLM Toolkit setup (Windows)
For the agent to work **inside the editor** (blueprints, assets, levels, UE
Python), the UE project needs the in-editor plugin. The short version:
**drop the plugin into the `Plugins/` folder — done.** No `.uproject` patching,
no enabling in the plugin browser.
### What the machine must have
| Component | What for | Note |
|---|---|---|
| Unreal Engine 5.7 | the editor itself | auto-detect via registry / default paths; otherwise `engineRoot` in `unreagent.local.yaml` |
| Visual Studio 2022 ("Game development with C++" workload) | **once** to build the plugin | the plugin ships as C++ source; UE asks "rebuild?" on first start |
| Node.js >= 18 | the plugin's stdio bridge | `npm install` for the bridge is done by the launcher automatically |
| Claude Code | the agent | `claude` on PATH; otherwise `agent.command` in local.yaml |
| `unreagent.exe` + `unreagent.yaml` | this launcher | see [Setup](#setup) |
### Steps
1. **Drop the plugin into the project** — copy or clone the repo/folder to
`<Project>/Plugins/UELLMToolkit/`. The `.uplugin` has
`EnabledByDefault: true` and activates its own engine dependencies
(EditorScriptingUtilities, Niagara, ControlRig, IKRig, ...) — **nothing**
to change on the `.uproject`.
2. **First editor start** — UE reports "Missing Modules ... rebuild?" -> Yes.
The build needs Visual Studio (once; afterwards the binaries live in the
plugin). Alternatively ahead of time via the launcher: console -> `c compile`.
3. **Done.** The plugin's HTTP server starts with the editor automatically on
port `3000`. The launcher forwards the bridge via `--mcp-config` to the
agent — for that, in `unreagent.yaml`:
```yaml
mcp:
enabled: true
extraServers:
ue-llm-toolkit:
command: node
args: ["${PROJECT_DIR}/Plugins/UELLMToolkit/Resources/mcp-bridge/index.js"]
env:
UNREAL_MCP_URL: "http://127.0.0.1:3000"
```
4. *Optional:* For `unreal_execute_script` with Python scripts, enable the UE
plugin **"Python Editor Script Plugin"** (Edit -> Plugins) — the other
tools don't need it.
What the launcher does **automatically** on startup (see above): install
bridge `node_modules`, check binary / script, `initialize` smoke test, wait
for port 3000 — every step with a clear message in `unreagent.log`.
### Access from another machine
The UE HTTP server binds to `localhost` by default. If a remote Claude session
should reach the in-editor server (port 3000) directly:
```ini
; Config/DefaultEngine.ini of the UE project
[HTTPServer.Listeners]
DefaultBindAddress=any
```
Plus a Windows firewall rule for port 3000 — and only in trusted networks,
the server controls the entire editor. On the other side `UNREAL_MCP_URL`
then points to `http://<windows-ip>:3000`.
### Troubleshooting: "Failed to reconnect: -32000"
`-32000` from Claude Code just means "bridge process immediately gone again"
— the real cause is in `unreagent.log` (check chain) or in Claude Code's MCP
logs (`%LOCALAPPDATA%\claude-cli-nodejs\Cache\<project>\mcp-logs-*`). Usual
suspects, in this order:
1. **Bridge path wrong** (`Cannot find module ...`) — check the path in
`extraServers` against the real plugin layout.
2. **Node missing** or not on PATH in the agent's session.
3. **`node_modules` missing** — this no longer happens, the launcher installs
them.
4. **Editor (not yet) reachable** — port 3000 only appears once the editor
is fully up; the launcher logs once it is.
## Configuration (quick reference)
| Section | Purpose |
|---|---|
| `agent` | agent command + args; `claudeIntegration` injects `--mcp-config` (+ permission tool); `window` (on by default) = interactive in the foreground (inherits console / TTY, launcher logs -> `unreagent.log`); on Windows, `claudeIntegration` additionally sets `CLAUDE_CODE_USE_POWERSHELL_TOOL=1` (`powershellTool: false` to disable) |
| `permissions` | permission layer: `allow_all` / `allowlist` / `deny_all`, plus `allow` / `deny` rules |
| `runtimes` | `python` (via `uv run`) and `node` for `run_python` / `run_node` |
| `mcp` | MCP server on/off, `address` (default `127.0.0.1:8765`), optional `token` (Bearer auth — auto-set by `hermes-setup`) |
| `unreal` | *optional* — editor args, restart policy (`never` / `on-failure` / `always`) |
| `commands` | *optional* — your own one-shot commands (compile / package are built-in) |
| `engineRoot` | *usually only in local.yaml* — engine path override |
### No process zombies (Windows)
Every running instance is attached to a **Windows Job Object** with
`KILL_ON_JOB_CLOSE`. If the handle closes — by clean stop, restart,
**or crash / hard-kill of the launcher** — the operating system terminates
the **entire process tree** (including ShaderCompileWorker etc.). OS-enforced,
no external dependency. On Linux (development) only the direct child is
killed.
### Crashes & recovery (unattended)
Two UE dialogs otherwise block agent-driven operation: the **crash reporter**
("Send and Restart / Close") and the **recovery prompt** on restart
("not closed cleanly, restore?"). The launcher therefore starts the editor
with **`-unattended`** — this suppresses **both** in UE 5.7 code (crash
dialog and both recovery systems: PackageAutoSaver *and* disaster recovery)
and discards the unclean state instead of asking.
| Option (`unreal:`) | Default | Effect |
|---|---|---|
| `unattended` | `true` | appends `-unattended` — no crash dialog, no recovery prompt |
| `killCrashReporter` | `true` | kills `CrashReportClientEditor.exe` before every (re)start (belt and suspenders) |
| `cleanOnRestart` | `false` | cleans `Saved/Autosaves/PackageRestoreData.json` + `Saved/Crashes/*` |
If a **human** is working interactively in the editor in parallel, set
`unattended: false`. In addition (so the reporter never even sends/starts)
in the project:
```ini
; Config/DefaultEngine.ini
[CrashReportClient]
bAgreeToCrashUpload=false
bImplicitSend=false
[/Script/DisasterRecoveryClient.DisasterRecoverClientConfig]
bIsEnabled=false
```
### Permission layer
With `permissions.enabled` + `agent.claudeIntegration` the launcher starts
the agent with `--permission-prompt-tool mcp__unreagent__approve`. Every
permission prompt from Claude Code then goes through the `approve` tool,
which decides per the configured policy. `mode: "allow_all"` = "auto-approve
everything" (with `deny` exceptions like `Bash(rm -rf *)`).
> Note: the exact **input** schema of `--permission-prompt-tool` is not
> officially documented by Anthropic. The `approve` handler reads the tool
> fields defensively (`tool_name` / `toolName` / `name`,
> `tool_input` / `input` / `arguments`). If a Claude Code update changes the
> format, only that one handler in `cmd/launcher/main.go` needs adapting.
### Notifying the user: taskbar flash (Windows)
When Claude Code stops a turn — finished working, or about to ask a
question — the terminal is often buried under other windows. On Windows,
`unreagent` can flash its console entry in the taskbar so the user notices.
**No setup needed.** When `agent.claudeIntegration: true` (the default in
`unreagent.example.yaml`), the launcher already passes the right flag to
Claude Code:
```text
claude --mcp-config '<…>' --settings '{"hooks":{"Stop":[{"hooks":[
{"type":"command","command":"\"<unreagent.exe>\" flash"}
]}]}}'
```
The `--settings` JSON is built by the launcher from
`os.Executable()` — so the hook command is a full, quoted path to
the running `unreagent.exe` and works even when `unreagent` is not on
the agent's `PATH`. It fires on every turn end, which is what we want:
the user gets feedback every time the agent yields, whether it's a
question, a decision point, or just "done".
What happens on `Stop`:
1. Claude Code spawns the hook command — `<unreagent.exe> flash`.
2. `unreagent flash` POSTs a `tools/call flash_task` request to the
running launcher (URL from `UNREAGENT_MCP_URL`, which the launcher
exports to the agent's env — the hook subprocess inherits it).
3. The launcher's `flash_task` MCP tool calls
`FlashWindowEx(FLASHW_TRAY | FLASHW_TIMERNOFG)` on the console HWND.
4. The taskbar entry flashes until the user clicks it — one call is
enough, the flash self-stops on activation.
The agent can also call `flash_task` **explicitly** via MCP when it
knows it's about to yield to the user (a question, a decision, a
completed piece of work the user should review). On non-Windows, the
call is a no-op.
To turn it off, disable `agent.claudeIntegration` (or wrap your own
`claude` invocation via `agent.command` + `agent.args` and skip the
auto-attached `--settings`). Manual test from a second shell:
```bash
unreagent flash # should start the flash; "[flash] console window flashing" appears in unreagent.log
```
### Hermes Agent (alternative agent)
[Hermes Agent](https://hermes-agent.nousresearch.com/docs/) (Nous Research)
is an autonomous agent with its own MCP client — useful as an alternative to
Claude Code. `unreagent` plugs in by registering the same MCP server
(`ue_start` / `ue_stop` / `logs` / `run_command` / `run_python` / …) under
Hermes' `mcp_servers:` block. The launcher does **not** auto-merge on
normal runs; you opt in once with a sub-command:
```yaml
# unreagent.yaml
agent:
hermes:
enabled: true
```
```bash
unreagent hermes-setup # 将 MCP 条目合并到 ~/.hermes/config.yaml
```
`hermes-setup` 操作的步骤如下:
1. 解析目标配置文件:显式 `agent.hermes.configPath` → `$HERMES_HOME/config.yaml` → `~/.hermes/config.yaml`。
2. 如果 `mcp.token` 为空,则生成 32 个随机字节(十六进制)并写入 `unreagent.local.yaml`(git 忽略)。启动器在下一次启动时读取它,以便令牌在重启后仍然存在。
3. 备份现有的 Hermes 配置文件到 `<path>.bak`(一次;永远不会覆盖以前的备份)。
4. 在 Hermes 配置文件中设置 `mcp_servers.unreagent: { url, headers: { Authorization: Bearer } }`。第二次运行时,它会覆盖该条目,而不是重复它 — 该操作是幂等的。
5. 原子写入文件(临时 + 重命名),以便在写入过程中发生崩溃时,不会留下半写配置文件。
MCP 服务器在设置 `mcp.token` 时强制执行 `Authorization: Bearer <token>`,包括对于嵌入式 Claude Code 代理,它在 `--mcp-config` 中自动获取匹配的 `headers:` 块。令牌检查使用 `crypto/subtle.ConstantTimeCompare`;空令牌默认值(历史行为)使服务器在 127.0.0.1 上保持开启。
运行 `hermes-setup` 后,请重启 Hermes 以使其获取新的条目。
### 代理运行时
`run_python` 通过 `uv run python` 执行代码 — uv 自动从 `pyproject.toml` / `requirements` 构建/同步虚拟环境,并在需要时安装正确的 Python 版本。`run_node` 在项目上下文中运行 JavaScript。代理**不需要**自己分析或设置环境;指令位于 MCP 工具描述中,因此自动位于上下文中。
## MCP 工具
| 工具 | 功能 |
|---|---|
| `status` | 进程状态 + 可用命令 |
| `ue_start` / `ue_stop` / `ue_restart` | 编辑器生命周期 |
| `run_command` | 运行预配置的命令(编译、打包) |
| `logs` | 服务的最后输出行 |
| `run_python` / `run_node` | 在准备好的环境中运行代码 |
| `read_file` / `list_dir` / `write_file` / `edit_file` | 项目上的文件访问(仅限 `files.enabled` / `-files`,作用于 `root`) |
| `approve` | Claude Code 的权限提示工具 |
| `flash_task` | 在 Windows 任务栏中闪烁 unreagent 终端(其他平台上为 no-op) |
## CLI 标志
| 标志 | 效果 |
|---|---|
| `-config <path>` | `unreagent.yaml` 的替代路径 |
| `-no-agent` | **不**启动代理 — 仅 UE + MCP 服务器。然后,外部代理(您自己的 Claude Code 会话)连接到 MCP 服务器。 |
| `-files` | 启用文件工具(`read_file` / `write_file` / `list_dir` / `edit_file`),即使在配置文件中关闭。 |
| `-write-mcp-config <path>` | 此外,将组装的 MCP 配置作为 `.mcp.json` 写入 `<path>`(用于外部客户端)。 |
## 子命令
| 子命令 | 目的 |
|---|---|
| `unreagent flash` | 向正在运行的启动器发送 `tools/call flash_task`,以便控制台窗口在任务栏中开始闪烁。由 Claude Code Stop 挂钩使用;也可以手动运行。 |
| `unreagent hermes-setup` | 将 unreagent MCP 服务器与 Hermes Agent 的 `config.yaml` 进行一次性合并(幂等;如果缺少,则自动生成 `mcp.token`)。参见上面的“Hermes Agent”。 |
### 代理运行方式(三种模式)
Claude Code 是一个交互式 TUI,需要 TTY — 作为原始后台子进程,它不会启动。因此,有三种方式:
1. **在前台交互式**(`agent.window`,**默认启用**)— 代理继承真正的控制台(TTY),并在启动 `unreagent` 的窗口中作为交互式 TUI 运行。UE + MCP 在后台运行,启动器日志记录到 `unreagent.log`(在项目旁边),以便代理拥有窗口。禁用方式为 `window: false`;在 `-p` 模式下关闭。
2. **无头/任务**(`agent.args: ["-p", "<task>"]`)— 无窗口一次性任务,然后代理退出。
3. **外部**(`-no-agent`)— 启动器不启动代理;您使用 `.mcp.json` / MCP 服务器连接自己的 Claude 会话。
### 无头/外部代理
使用 `-no-agent`,所有操作都在没有嵌入式代理的情况下运行 — UE + MCP 服务器。这就是您如何将自己的 Claude Code 连接到项目“在没有人本地存在的情况下”:
```bash
unreagent.exe -no-agent -files # UE + MCP + 文件工具,无嵌入式代理
# 在您的 Claude Code 会话中:
claude --mcp-config '{"mcpServers":{"unreagent":{"type":"http","url":"http://127.0.0.1:8765/mcp"}}}'
```
要从另一台机器访问,请将 `mcp.address` 设置为 `0.0.0.0:8765`(仅限受信任的网络 — 然后服务器具有文件写入访问权限)。
## 手动控制(标准输入)
`status` · `r [name]`(重启)· `start <name>` · `stop <name>` ·
`c <name>`(运行命令)· `q`(退出)
## 手动 MCP 测试
```bash
curl -s -X POST http://127.0.0.1:8765/mcp -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器