@uemcp/server 0.1.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -0
- package/dist/index.mjs +2435 -83
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,10 +16,21 @@ The MCP tool surface currently exposes:
|
|
|
16
16
|
|
|
17
17
|
- `get_project_id`
|
|
18
18
|
- `add_module`
|
|
19
|
+
- `list_registered_projects`
|
|
19
20
|
- `ping_project`
|
|
21
|
+
- `wait_for_job`
|
|
20
22
|
- `build_target`
|
|
21
23
|
- `generate_project_files`
|
|
22
24
|
- `clean_build`
|
|
25
|
+
- `cook_content`
|
|
26
|
+
- `package_project`
|
|
27
|
+
- `run_editor_commandlet`
|
|
28
|
+
- `get_package_status`
|
|
29
|
+
- `cancel_package_job`
|
|
30
|
+
- `analyze_trace`
|
|
31
|
+
- `get_config_value`
|
|
32
|
+
- `set_config_value`
|
|
33
|
+
- `list_config_section`
|
|
23
34
|
- `run_clang_tidy`
|
|
24
35
|
|
|
25
36
|
## Project Identity
|
|
@@ -55,6 +66,14 @@ This is intended to let an MCP client resolve the canonical Unreal project ident
|
|
|
55
66
|
- Refuses to overwrite an existing module name or `Source/<moduleName>` directory
|
|
56
67
|
- Pure filesystem scaffold; it does not consult the registry or a running editor. Run project-file generation and a build afterward to compile the module.
|
|
57
68
|
|
|
69
|
+
### `list_registered_projects`
|
|
70
|
+
|
|
71
|
+
- Returns every Unreal Editor currently registered with this server: `projectId`, `projectName`, `projectPath`, `host` (always `127.0.0.1`), `port`, `pid`, `registeredAt`, `lastSeenAt`
|
|
72
|
+
- Discovery entry point — call it before the project-scoped tools instead of guessing a `projectId`
|
|
73
|
+
- Default path performs no network I/O; `verify: true` pings each returned entry once, concurrently, and adds `reachable` (plus `reason` when unreachable) under an overall time budget
|
|
74
|
+
- Bounded by `maxResults` (default 50) with `totalCount` and `truncated`; ordering is stable across calls
|
|
75
|
+
- Never mutates the registry — `ping_project` owns stale-registration eviction
|
|
76
|
+
|
|
58
77
|
### `ping_project`
|
|
59
78
|
|
|
60
79
|
- Accepts a canonical `projectId`
|
|
@@ -67,6 +86,14 @@ This is intended to let an MCP client resolve the canonical Unreal project ident
|
|
|
67
86
|
- Uses retry delays of approximately `500ms`, `1s`, and `2s`
|
|
68
87
|
- Removes the stale registration from memory and persists the cleanup if the bridge remains dead after retries
|
|
69
88
|
|
|
89
|
+
### `wait_for_job`
|
|
90
|
+
|
|
91
|
+
- Blocks server-side until an async job reaches a terminal state, then returns the payload the matching `get_*_status` tool would have returned under `status`
|
|
92
|
+
- Replaces a client-side poll loop for `kind` `compile` (`trigger_live_compile` / `trigger_hot_reload`), `create_class`, `test_run`, and `pie`; `kind` is required because job ids are bare GUIDs
|
|
93
|
+
- `outcome` is `completed`, `timed_out` (last observed status still returned), or `unknown_job` (unknown/expired/cancelled id, returned on the first poll)
|
|
94
|
+
- Per-kind default budgets (compile 120s, create_class 60s, pie 30s, test_run 300s), clamped to 600s; polls back off from 250ms (1s for test runs) and a transient bridge read timeout or dropped socket is retried rather than failing the wait
|
|
95
|
+
- For `test_run`, `status.results` is the first page — page the rest with `get_test_run_status` and `nextCursor`
|
|
96
|
+
|
|
70
97
|
### `build_target` / `generate_project_files` / `clean_build`
|
|
71
98
|
|
|
72
99
|
- Resolve the engine from the `.uproject` `EngineAssociation` (or an explicit `engineVersion`) under `UE_INSTALLS_DIR`
|
|
@@ -75,12 +102,71 @@ This is intended to let an MCP client resolve the canonical Unreal project ident
|
|
|
75
102
|
- `build_target`/`clean_build` refuse to run when the project's editor is registered and reachable (link conflicts on Windows, ignored output on macOS/Linux) — pass `force: true` to override
|
|
76
103
|
- Do not require a running editor
|
|
77
104
|
|
|
105
|
+
### `cook_content` / `package_project` / `run_editor_commandlet` / `get_package_status` / `cancel_package_job`
|
|
106
|
+
|
|
107
|
+
- Resolve `UnrealEditor-Cmd` and AutomationTool from the same `resolveEngine` result the build tools use, and take no new configuration. On macOS/Linux AutomationTool is `RunUAT.sh` (never the `RunUAT.command` Finder wrapper). On Windows it is `AutomationTool.dll` run under the engine-bundled `dotnet` (falling back to `dotnet` on `PATH` if the bundle is absent), because Node cannot spawn a `.bat` without a shell and routing these arguments through `cmd.exe` would put project and archive paths through a command interpreter. That path builds the child environment `RunUAT.bat` would have: the bundled runtime prepended to `PATH`, `DOTNET_ROOT` / `DOTNET_MULTILEVEL_LOOKUP` / `DOTNET_ROLL_FORWARD` set, and the Visual Studio variables a developer shell exports cleared so they cannot steer .NET resolution
|
|
108
|
+
- `cook_content` runs a full offline cook (`-run=Cook -TargetPlatform=<platform>`, `-CookAll` or `-Map=A+B`); `package_project` runs AutomationTool `BuildCookRun` (build + cook + stage + pak + archive)
|
|
109
|
+
- Both return a `packageJobId` immediately and run in the background — expect **minutes to tens of minutes** (default timeout 90 minutes, ceiling 2 hours). Poll with `get_package_status`, abort with `cancel_package_job`
|
|
110
|
+
- One job at a time per project, and four across the server: a second job for the same project contends for the same `Saved/`, `Intermediate/` and DDC paths, so it is refused rather than queued. Cancel the running one with `cancel_package_job` if you need the slot
|
|
111
|
+
- `get_package_status` reports `running` / `succeeded` / `failed` / `cancelled` / `unknown_job` plus structured cook/UAT diagnostics, an output tail that fills while the job runs, truncation flags, exit code and timing. `kind` is absent for `unknown_job` — a bare job id carries no kind. `cancel_package_job` marks the job cancelled but leaves it `running` until the process actually exits, so a caller cannot start a conflicting job while the old one is still writing. Diagnostics are collected by streaming the log as it is produced (cook errors are spread across the whole run, so a retained tail alone would lose them); the retained raw text is a tail and is capped
|
|
112
|
+
- `run_editor_commandlet` runs one commandlet by name. Allowed: a name of identifier characters only (`ResavePackages`, `FixupRedirects`, `DataValidation`, …) plus arguments as separate argv entries — no shell is involved. Rejected: names with path separators, whitespace or switch prefixes, and arguments overriding `-run=` or `-Project=`. There is no allowlist of commandlets, so note that **a commandlet can modify or delete project assets**
|
|
113
|
+
- `cook_content`, `package_project` and `run_editor_commandlet` refuse to run while the project's editor is registered and reachable (a second editor process contends for the same `Saved/`, `Intermediate/` and DDC paths, and write-capable commandlets race packages the live editor holds in memory) — pass `force: true` to override
|
|
114
|
+
|
|
115
|
+
### `analyze_trace`
|
|
116
|
+
|
|
117
|
+
- Turns the `.utrace` that `stop_insights_trace` hands back into a readable summary; the binary container is otherwise a dead end for an assistant
|
|
118
|
+
- Runs the engine's `UnrealInsights` headlessly (`-OpenTraceFile=… -ExecOnAnalysisCompleteCmd=… -AutoQuit`), exports CSVs into a temp directory, parses them, and deletes the directory on every exit path including timeout and analyzer failure
|
|
119
|
+
- The analyzer is located through the same `resolveEngine` discovery as the build tools (`engineVersion` or `projectPath`) — no new path configuration
|
|
120
|
+
- Returns frame-time p50/p90/p99, the worst N hitches with timestamps, a CPU vs GPU split, and top-N scopes by inclusive and exclusive time. Every list is top-N, the response is byte-capped, and `truncated` names what was shortened
|
|
121
|
+
- Memory and LLM-tag totals are **not** reported: they do not fall out of this analysis pass
|
|
122
|
+
- Distinct failures: a missing/unreadable `.utrace` and an analyzer timeout or non-zero exit each raise their own error; an engine install with no Insights binary returns `state: 'unsupported'` with a reason instead of failing
|
|
123
|
+
|
|
124
|
+
### `get_config_value` / `set_config_value` / `list_config_section`
|
|
125
|
+
|
|
126
|
+
- Read and write project ini settings through the **running editor's** config system rather than by touching files: the engine resolves the `Base*` → `Default*` → `Saved/Config` hierarchy and the `+`/`-`/`.`/`!` array operators, and a live editor rewrites its config on save, so a raw file write behind it is clobbered
|
|
127
|
+
- `file` is a closed enum (`Engine`, `Game`, `Input`, `Editor`, `EditorPerProjectUserSettings`) — the tools cannot be pointed at an arbitrary path
|
|
128
|
+
- `get_config_value` returns the resolved value, the ini it came from, and `resolvedFrom` (`engine_base` / `project_default` / `saved_user`), and reports `present: false` for an unset key so absent is distinguishable from present-but-empty
|
|
129
|
+
- `set_config_value` writes `Config/Default<File>.ini` by default (or the `Saved/Config` override with `scope: 'saved_user'`), flushes, mirrors the write into the merged runtime config so the live editor sees it without a restart, and returns the previous value. That previous value is the one the editor resolved across the whole hierarchy, not the contents of the layer being written: if it came from an engine or default layer, writing it back leaves the key materialised in the target layer rather than restoring the original state
|
|
130
|
+
- Values longer than the protocol bound (4096 characters — reachable with asset path lists and accumulated `+Array=` entries) come back shortened with `valueTruncated` / `previousValueTruncated` set, rather than failing the whole call
|
|
131
|
+
- `list_config_section` dumps one section as key/value pairs, sorted for a stable cursor and paged with `nextCursor` / `totalCount` / `truncated`
|
|
132
|
+
- All three require a registered editor; without one, `set_config_value` explains why instead of writing the file behind the editor's back
|
|
133
|
+
|
|
78
134
|
### `run_clang_tidy`
|
|
79
135
|
|
|
80
136
|
- Reads the synced `compile_commands.json` from the provided `compileCommandsDir`
|
|
81
137
|
- Spawns `clang-tidy` (server-configured via `CLANG_TIDY_PATH`, else `PATH`) over the translation units and returns structured findings (file, line, column, check, severity, message)
|
|
82
138
|
- Report only — does not apply fixes
|
|
83
139
|
|
|
140
|
+
## MCP Resources
|
|
141
|
+
|
|
142
|
+
Resources carry context a client can attach without spending a tool call and a model turn. Every one of them is served from in-memory state or the local filesystem: **a resource read never opens a bridge socket and never waits on the editor**, so an unreachable or absent editor produces a structured payload rather than a timeout.
|
|
143
|
+
|
|
144
|
+
All resources use the `uemcp://` URI scheme, are `application/json`, and are capped at 64 KB (`MAX_RESOURCE_PAYLOAD_BYTES`). A read that hits the cap drops entries from the end of the list and sets `truncated: true` while `totalCount` stays authoritative — the same convention the paged tools use. The authority segment (`registry`, `engines`, `workspace`) is the type discriminator.
|
|
145
|
+
|
|
146
|
+
| URI | Contents |
|
|
147
|
+
| --- | --- |
|
|
148
|
+
| `uemcp://registry` | Every registered Unreal Editor: `projectId`, `projectName`, `projectPath`, `port`, `pid`, `registeredAt`, `lastSeenAt`, plus `totalCount` and `truncated` |
|
|
149
|
+
| `uemcp://registry/{projectId}` | One registration, addressed by canonical ProjectID. Enumerable — the template's `list` callback returns the current registry, and `projectId` offers completions from it |
|
|
150
|
+
| `uemcp://engines` | Unreal installs found under `UE_INSTALLS_DIR`, ascending by version, each with `engineVersion`, `engineRoot`, and `unrealBuildToolResolved`. Source builds and GUID engine associations are excluded, matching `resolveEngine` |
|
|
151
|
+
| `uemcp://workspace/project` | The Unreal project identity of the server's working directory, resolved from `Config/DefaultGame.ini` |
|
|
152
|
+
|
|
153
|
+
`uemcp://registry/{projectId}` reports `found: false` with a `reason` for an unknown or malformed ProjectID, and `uemcp://engines` / `uemcp://workspace/project` report a `reason` when `UE_INSTALLS_DIR` is unset or the directory is not an Unreal project — none of them fail the read.
|
|
154
|
+
|
|
155
|
+
Registering or deregistering an editor over the HTTP API emits `notifications/resources/list_changed`. There is no `resources/subscribe`: the MCP SDK's `McpServer` does not implement it, so the list-changed notification is the only change signal.
|
|
156
|
+
|
|
157
|
+
## MCP Prompts
|
|
158
|
+
|
|
159
|
+
Prompts ship the workflows this server knows about as discoverable entry points for any MCP client, not just ones that can read the repo's `.agents/skills/` runbooks. They are compiled into the bundle, reference only tools this server registers, and assume no shell, no checkout, and no particular client.
|
|
160
|
+
|
|
161
|
+
| Prompt | Arguments | What it drives |
|
|
162
|
+
| --- | --- | --- |
|
|
163
|
+
| `diagnose_crash` | `projectId` | Registration check, `get_crash_callstack`, `get_editor_state`, then the output log around the crash |
|
|
164
|
+
| `diagnose_bridge_disconnect` | `projectPath` (optional) | Which leg of the bridge is broken: registry state, `get_project_id` cross-check, `ping_project`, plugin-side log evidence |
|
|
165
|
+
| `smoke_test_bridge` | `projectId` | Green-path proof: registration, `ping_project`, `get_editor_state`, and a full log subscribe-emit-drain cycle |
|
|
166
|
+
| `add_gameplay_feature` | `projectId`, `className`, `parentClass`, `moduleName` (optional) | `create_cpp_class` → `wait_for_job` → implement → `trigger_live_compile` → `run_automation_tests` → `save_dirty_packages` |
|
|
167
|
+
|
|
168
|
+
`diagnose_bridge_disconnect` and `smoke_test_bridge` are distilled from the same-named skills in `.agents/skills/`. Those files stay separate on purpose: they are maintainer runbooks that reference `just` recipes, `registry.json` paths, and pino module loggers, and the published package ships `dist` only, so they cannot be read at runtime. Each skill carries a pointer to its prompt counterpart so the two do not silently diverge.
|
|
169
|
+
|
|
84
170
|
## Configuration
|
|
85
171
|
|
|
86
172
|
- Engine toolchain (`build_target`, `generate_project_files`, `clean_build`):
|