@uemcp/server 1.0.0 → 1.2.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.
Files changed (3) hide show
  1. package/README.md +48 -2
  2. package/dist/index.mjs +1054 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -44,6 +44,10 @@ The MCP tool surface currently exposes:
44
44
  - `redo`
45
45
  - `open_level`
46
46
  - `get_open_level_status`
47
+ - `get_source_control_status`
48
+ - `refresh_source_control_status`
49
+ - `checkout_assets`
50
+ - `get_source_control_job_status`
47
51
 
48
52
  ## Project Identity
49
53
 
@@ -101,9 +105,9 @@ This is intended to let an MCP client resolve the canonical Unreal project ident
101
105
  ### `wait_for_job`
102
106
 
103
107
  - 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`
104
- - 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
108
+ - Replaces a client-side poll loop for `kind` `compile` (`trigger_live_compile` / `trigger_hot_reload`), `create_class`, `test_run`, `pie`, and `source_control` (`checkout_assets` / `refresh_source_control_status`); `kind` is required because job ids are bare GUIDs
105
109
  - `outcome` is `completed`, `timed_out` (last observed status still returned), or `unknown_job` (unknown/expired/cancelled id, returned on the first poll)
106
- - 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
110
+ - Per-kind default budgets (compile 120s, create_class 60s, pie 30s, source_control 60s, 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
107
111
  - For `test_run`, `status.results` is the first page — page the rest with `get_test_run_status` and `nextCursor`
108
112
 
109
113
  ### `build_target` / `generate_project_files` / `clean_build`
@@ -182,6 +186,48 @@ This is intended to let an MCP client resolve the canonical Unreal project ident
182
186
  - Actor paths do not survive a load: re-run `list_actors` against the new world rather than reusing paths from the old one
183
187
  - One load at a time per editor; a second request while one is running is refused with `open_level_in_progress` rather than queued
184
188
 
189
+ ### `get_source_control_status` / `refresh_source_control_status` / `checkout_assets` / `get_source_control_job_status`
190
+
191
+ - The prerequisite for every asset write on a team project. Under Perforce or Git-LFS file locking a `.uasset` stays read-only until it is checked out, so a write that skips this fails later at save time — several tool calls away from its cause
192
+ - `get_source_control_status` answers **inline**: it reads the provider's own state cache, which is a memory lookup rather than a server round trip. It reports the `provider` name, `enabled` (is source control configured at all), `available` (is it currently connected), and one entry per requested path with a `state` of `checked_out_by_me`, `checked_out_by_other` (plus `checkedOutBy` naming the holder), `marked_for_add`, `not_checked_out`, `not_in_depot`, or `unknown`
193
+ - `enabled` is what separates "no source control configured" from "configured, and this file is simply not checked out". Those are never collapsed into one answer
194
+ - A file the editor has never asked the server about reports `unknown`, **not** a guess. `refresh_source_control_status` is how it becomes a real answer. A path that resolves to no package on disk also reports `unknown`, but carries an `error` saying so — an unresolvable path never passes as a depot answer
195
+ - `refresh_source_control_status` and `checkout_assets` talk to the source control server over the network, which runs far past the plugin's 5 s game-thread budget, so neither runs inline: each returns `state: 'accepted'` with a `sourceControlJobId`. Poll it with `get_source_control_job_status`, or wait with `wait_for_job` using `kind: 'source_control'`
196
+ - The poll reports `successCount` / `failureCount` and one result per file with its post-operation `state`, so a checkout is confirmed without a second query. One asset the provider refuses does not abort the rest — it reports `ok: false` with its own `error`
197
+ - Checkout only. Submitting is never done for you, and a checked-out asset stays checked out until a human submits or reverts it
198
+ - A request against an editor with no provider configured is refused with `source_control_disabled`, and one against a configured-but-offline provider with `source_control_unavailable`. Neither degrades into a filesystem check
199
+ - In a non-editor plugin build all four report `state: 'unsupported'` and schedule nothing
200
+
201
+ ### `read_data_table` / `write_data_table_row`
202
+
203
+ - A DataTable is where a project keeps its tuning data, and it is a binary `.uasset` — `find_assets` can locate one but nothing can read it off disk, so these are the only way in. `read_data_table` loads the asset and reports the row struct it is typed on, an ordered column list (`{ name, type }`, using the same type rendering `get_class_reflection` emits), and each row as a column-name-to-value map
204
+ - Values are `ExportText`, the same format `get_actor_details` and `get_cdo_defaults` emit and `set_actor_property` accepts, so a value read here writes straight back with no translation. Column names are the **authored** names, so a Blueprint (user-defined) row struct reports the names the editor shows rather than its mangled internal ones
205
+ - Rows page in **name order** — `RowMap` iteration follows hashing, which shifts as rows are added, so a cursor over it would skip or repeat rows between pages. Pages are trimmed by encoded bytes as well as by `maxRows`, because one row of a wide tuning struct can cost more than a whole page of asset entries; `totalRowCount` stays authoritative when a page truncates
206
+ - `loadIfNeeded: false` refuses to load an unloaded table and reports `asset_not_loaded`. Unlike `get_blueprint_summary` there is no metadata-only fallback: a DataTable carries no Asset Registry tags describing its rows
207
+ - `write_data_table_row` upserts or deletes one row. **Every column name and value is validated against the row struct before anything is written**, into scratch memory that is only copied in once the whole row imports, so a rejected write leaves the table byte-identical — there is no partial application. An unknown column reports `unknown_column` and lists the valid ones; a value that will not parse reports `invalid_row_value` and names the field and its type
208
+ - Numeric and boolean values are range-checked **before** `ImportText` rather than after: the engine's importer takes `not-a-number` as `0` and reports success, and silent coercion is exactly the failure a tuning-data write cannot afford
209
+ - The write runs inside a single `FScopedTransaction` (Ctrl+Z reverses it) and marks the package dirty **without saving** — compose with `save_dirty_packages`. `dryRun: true` validates everything and reports `state: 'validated'`, opening no transaction
210
+ - Deleting a row that does not exist is **not** a failure: it reports `rowExisted: false` with an explanatory message, because the table is already in the requested state
211
+ - A `.uasset` sits read-only on disk until it is checked out under Perforce or Git-LFS file locking. That case is detected before anything is mutated rather than surfacing later as an unexplained save error. Where a provider is configured the answer comes from the provider itself — `source_control_checkout_required` for a file that is simply not checked out, `source_control_checked_out_by_other` naming the holder when someone else has it — and only where none is does it fall back to the on-disk read-only check. Fix either with `checkout_assets`
212
+ - In a non-editor plugin build `read_data_table` fails with `unsupported` and `write_data_table_row` returns `state: 'unsupported'`
213
+
214
+ ### `export_data_table` / `import_data_table`
215
+
216
+ - The bulk half, built on the engine's own JSON and CSV serializers rather than the per-row walk above
217
+ - `export_data_table` returns the serialized table in **chunks by character offset**, because the engine produces one string rather than anything row-addressable. Concatenate chunks in `nextCursor` order to reassemble; a single chunk is not valid JSON or CSV on its own. Chunks are cut by encoded byte cost and never inside a surrogate pair
218
+ - `import_data_table` **replaces every row** — that is what the engine's importer does, so a row absent from the content is a row deleted. Use `write_data_table_row` to change one value. It runs in one transaction and dirties the package without saving, exactly like the per-row write
219
+ - Rows the importer rejects come back in `problems` while the rest still apply, so check `problemCount` even on success. `dryRun: true` runs the import against a transient duplicate and reports the resulting row count and problems without touching the real table
220
+ - The whole payload travels in one 64 KiB bridge frame, so `content` is capped at `BRIDGE_DATA_TABLE_MAX_IMPORT_CHARS` (16 K characters, sized so worst-case UTF-8 expansion still fits). A table larger than that cannot be imported this way
221
+
222
+ ### `create_data_table` / `rename_data_table` / `delete_data_table` / `rename_data_table_row`
223
+
224
+ - The lifecycle half: the tools above all require a table that already exists, and until these landed nothing could make one over the bridge
225
+ - `create_data_table` binds a row struct at creation and never after — `UDataTable::RowStruct` can be reassigned, but that reinterprets existing row memory through the new layout, so the bridge does not expose it. Native structs are addressed as `/Script/<Module>.<Struct>`, a Blueprint struct by its object path; anything that is neither an `FTableRowBase` child nor a user-defined struct reports `invalid_row_struct` rather than producing a table that accepts no rows. The new table is dirty and **unsaved**, like every other DataTable write
226
+ - `rename_data_table` renames, moves, or both — one operation to the asset tools. References from other assets are fixed up and a redirector is left at the old path, so existing hard references keep resolving. `newName` must be a bare asset name: one carrying a path separator is refused rather than silently relocating the asset somewhere the caller never named
227
+ - `delete_data_table` is **the one DataTable tool with no transaction behind it** — asset deletion is not undoable, and there is no dirty-package step to reconsider at. A table other assets reference is refused with `asset_referenced` and its referencers reported; `force` deletes anyway and breaks them
228
+ - `rename_data_table_row` keeps the row's position and values, which an upsert-under-the-new-name plus a delete-of-the-old does not: that pair costs two transactions and moves the row to the end. It refuses a missing row (`row_not_found`) and a name already taken (`row_already_exists`), since renaming onto an existing row would discard that row's values. **Row names are how other assets address rows**, so a rename breaks any `FDataTableRowHandle` pointing at the old name — nothing fixes those up
229
+ - Rename and delete both need referencer data, which is only trustworthy once the Asset Registry's initial gather has finished — mid-scan `GetReferencers` returns a short list that reads as "nothing depends on this". Both drive the scan to completion first (starting it when a headless editor never did), and report `asset_registry_scanning` only if it is still incomplete afterwards. On a large project that wait can be long enough to hit the bridge read timeout
230
+
185
231
  ## MCP Resources
186
232
 
187
233
  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.
package/dist/index.mjs CHANGED
@@ -459,6 +459,45 @@ const BRIDGE_OPEN_LEVEL_STATUS_STATES = [
459
459
  "failed",
460
460
  "unknown_job"
461
461
  ];
462
+ const BRIDGE_READ_DATA_TABLE_REQUEST_TYPE = "read_data_table";
463
+ const BRIDGE_WRITE_DATA_TABLE_ROW_REQUEST_TYPE = "write_data_table_row";
464
+ const BRIDGE_EXPORT_DATA_TABLE_REQUEST_TYPE = "export_data_table";
465
+ const BRIDGE_IMPORT_DATA_TABLE_REQUEST_TYPE = "import_data_table";
466
+ const BRIDGE_CREATE_DATA_TABLE_REQUEST_TYPE = "create_data_table";
467
+ const BRIDGE_RENAME_DATA_TABLE_REQUEST_TYPE = "rename_data_table";
468
+ const BRIDGE_DELETE_DATA_TABLE_REQUEST_TYPE = "delete_data_table";
469
+ const BRIDGE_RENAME_DATA_TABLE_ROW_REQUEST_TYPE = "rename_data_table_row";
470
+ const BRIDGE_DATA_TABLE_VALUE_MAX_CHARS = 4096;
471
+ const BRIDGE_DATA_TABLE_ROW_OPERATIONS = ["upsert", "delete"];
472
+ const BRIDGE_DATA_TABLE_WRITE_STATES = [
473
+ "applied",
474
+ "validated",
475
+ "unsupported"
476
+ ];
477
+ const BRIDGE_DATA_TABLE_FORMATS = ["json", "csv"];
478
+ const BRIDGE_DATA_TABLE_MAX_EXPORT_CHARS = 24576;
479
+ const BRIDGE_DATA_TABLE_MAX_IMPORT_CHARS = 16384;
480
+ const BRIDGE_GET_SOURCE_CONTROL_STATUS_REQUEST_TYPE = "get_source_control_status";
481
+ const BRIDGE_REFRESH_SOURCE_CONTROL_STATUS_REQUEST_TYPE = "refresh_source_control_status";
482
+ const BRIDGE_CHECKOUT_ASSETS_REQUEST_TYPE = "checkout_assets";
483
+ const BRIDGE_GET_SOURCE_CONTROL_JOB_STATUS_REQUEST_TYPE = "get_source_control_job_status";
484
+ const BRIDGE_SOURCE_CONTROL_FILE_STATES = [
485
+ "checked_out_by_me",
486
+ "checked_out_by_other",
487
+ "marked_for_add",
488
+ "not_checked_out",
489
+ "not_in_depot",
490
+ "unknown"
491
+ ];
492
+ const BRIDGE_SOURCE_CONTROL_STATUS_STATES = ["reported", "unsupported"];
493
+ const BRIDGE_SOURCE_CONTROL_JOB_ACCEPT_STATES = ["accepted", "unsupported"];
494
+ const BRIDGE_SOURCE_CONTROL_JOB_STATES = [
495
+ "running",
496
+ "succeeded",
497
+ "failed",
498
+ "unknown_job"
499
+ ];
500
+ const BRIDGE_SOURCE_CONTROL_OPERATIONS = ["checkout", "refresh"];
462
501
  const requestIdSchema = z.string().trim().min(1);
463
502
  const nonEmptyStringSchema = z.string().trim().min(1);
464
503
  const boundedStringSchema = z.string().trim().max(512);
@@ -568,6 +607,22 @@ const openLevelJobIdSchema = z.string().trim().min(1);
568
607
  const levelPathSchema = z.string().trim().min(1).max(512);
569
608
  const actorPathBatchSchema = z.array(actorPathSchema).min(1).max(64);
570
609
  const actorTagArraySchema = z.array(boundedStringSchema).max(16);
610
+ const dataTableCursorSchema = z.number().int().min(0);
611
+ const dataTableMaxRowsSchema = z.number().int().min(1).max(100);
612
+ const dataTableRowNameSchema = z.string().trim().min(1).max(256);
613
+ const dataTableRowOperationSchema = z.enum(BRIDGE_DATA_TABLE_ROW_OPERATIONS);
614
+ const dataTableWriteStateSchema = z.enum(BRIDGE_DATA_TABLE_WRITE_STATES);
615
+ const dataTableFormatSchema = z.enum(BRIDGE_DATA_TABLE_FORMATS);
616
+ const dataTableValueMapSchema = z.record(z.string().trim().min(1).max(256), z.string().max(BRIDGE_DATA_TABLE_VALUE_MAX_CHARS)).refine((value) => Object.keys(value).length <= 200, { message: `A write carries at most ${String(200)} columns.` });
617
+ const dataTableImportContentSchema = z.string().min(1).max(BRIDGE_DATA_TABLE_MAX_IMPORT_CHARS);
618
+ const dataTableAssetNameSchema = z.string().trim().min(1).max(256).refine((value) => !value.includes("/") && !value.includes("."), { message: "An asset name must not contain a path separator; use destinationPath to move the asset." });
619
+ const sourceControlAssetPathBatchSchema = z.array(assetPathSchema).min(1).max(64);
620
+ const sourceControlFileStateSchema = z.enum(BRIDGE_SOURCE_CONTROL_FILE_STATES);
621
+ const sourceControlStatusStateSchema = z.enum(BRIDGE_SOURCE_CONTROL_STATUS_STATES);
622
+ const sourceControlJobAcceptStateSchema = z.enum(BRIDGE_SOURCE_CONTROL_JOB_ACCEPT_STATES);
623
+ const sourceControlJobStateSchema = z.enum(BRIDGE_SOURCE_CONTROL_JOB_STATES);
624
+ const sourceControlOperationSchema = z.enum(BRIDGE_SOURCE_CONTROL_OPERATIONS);
625
+ const sourceControlJobIdSchema = z.string().trim().min(1);
571
626
  const vectorInputSchema = z.object({
572
627
  x: z.number().default(0),
573
628
  y: z.number().default(0),
@@ -972,6 +1027,111 @@ const bridgeGetOpenLevelStatusRequestSchema = z.object({
972
1027
  type: z.literal(BRIDGE_GET_OPEN_LEVEL_STATUS_REQUEST_TYPE),
973
1028
  version: z.literal(2)
974
1029
  });
1030
+ const bridgeReadDataTableRequestSchema = z.object({
1031
+ assetPath: assetPathSchema,
1032
+ cursor: dataTableCursorSchema.default(0),
1033
+ loadIfNeeded: z.boolean().default(true),
1034
+ maxRows: dataTableMaxRowsSchema,
1035
+ rowNameFilter: boundedStringSchema.optional(),
1036
+ requestId: requestIdSchema,
1037
+ type: z.literal(BRIDGE_READ_DATA_TABLE_REQUEST_TYPE),
1038
+ version: z.literal(2)
1039
+ });
1040
+ const bridgeWriteDataTableRowRequestSchema = z.object({
1041
+ assetPath: assetPathSchema,
1042
+ dryRun: z.boolean().default(false),
1043
+ operation: dataTableRowOperationSchema,
1044
+ rowName: dataTableRowNameSchema,
1045
+ values: dataTableValueMapSchema.optional(),
1046
+ requestId: requestIdSchema,
1047
+ type: z.literal(BRIDGE_WRITE_DATA_TABLE_ROW_REQUEST_TYPE),
1048
+ version: z.literal(2)
1049
+ }).refine((value) => value.operation !== "upsert" || value.values !== void 0, {
1050
+ message: "A write_data_table_row upsert requires values.",
1051
+ path: ["values"]
1052
+ }).refine((value) => value.operation !== "delete" || value.values === void 0, {
1053
+ message: "A write_data_table_row delete must not carry values.",
1054
+ path: ["values"]
1055
+ });
1056
+ const bridgeExportDataTableRequestSchema = z.object({
1057
+ assetPath: assetPathSchema,
1058
+ cursor: dataTableCursorSchema.default(0),
1059
+ format: dataTableFormatSchema.default("json"),
1060
+ loadIfNeeded: z.boolean().default(true),
1061
+ requestId: requestIdSchema,
1062
+ type: z.literal(BRIDGE_EXPORT_DATA_TABLE_REQUEST_TYPE),
1063
+ version: z.literal(2)
1064
+ });
1065
+ const bridgeImportDataTableRequestSchema = z.object({
1066
+ assetPath: assetPathSchema,
1067
+ content: dataTableImportContentSchema,
1068
+ dryRun: z.boolean().default(false),
1069
+ format: dataTableFormatSchema.default("json"),
1070
+ requestId: requestIdSchema,
1071
+ type: z.literal(BRIDGE_IMPORT_DATA_TABLE_REQUEST_TYPE),
1072
+ version: z.literal(2)
1073
+ });
1074
+ const bridgeCreateDataTableRequestSchema = z.object({
1075
+ assetPath: assetPathSchema,
1076
+ dryRun: z.boolean().default(false),
1077
+ rowStructPath: assetPathSchema,
1078
+ requestId: requestIdSchema,
1079
+ type: z.literal(BRIDGE_CREATE_DATA_TABLE_REQUEST_TYPE),
1080
+ version: z.literal(2)
1081
+ });
1082
+ const bridgeRenameDataTableRequestSchema = z.object({
1083
+ assetPath: assetPathSchema,
1084
+ destinationPath: assetPathSchema.optional(),
1085
+ dryRun: z.boolean().default(false),
1086
+ newName: dataTableAssetNameSchema.optional(),
1087
+ requestId: requestIdSchema,
1088
+ type: z.literal(BRIDGE_RENAME_DATA_TABLE_REQUEST_TYPE),
1089
+ version: z.literal(2)
1090
+ }).refine((value) => value.newName !== void 0 || value.destinationPath !== void 0, {
1091
+ message: "A rename_data_table requires a newName, a destinationPath, or both.",
1092
+ path: ["newName"]
1093
+ });
1094
+ const bridgeDeleteDataTableRequestSchema = z.object({
1095
+ assetPath: assetPathSchema,
1096
+ dryRun: z.boolean().default(false),
1097
+ force: z.boolean().default(false),
1098
+ requestId: requestIdSchema,
1099
+ type: z.literal(BRIDGE_DELETE_DATA_TABLE_REQUEST_TYPE),
1100
+ version: z.literal(2)
1101
+ });
1102
+ const bridgeRenameDataTableRowRequestSchema = z.object({
1103
+ assetPath: assetPathSchema,
1104
+ dryRun: z.boolean().default(false),
1105
+ newRowName: dataTableRowNameSchema,
1106
+ rowName: dataTableRowNameSchema,
1107
+ requestId: requestIdSchema,
1108
+ type: z.literal(BRIDGE_RENAME_DATA_TABLE_ROW_REQUEST_TYPE),
1109
+ version: z.literal(2)
1110
+ });
1111
+ const bridgeGetSourceControlStatusRequestSchema = z.object({
1112
+ assetPaths: sourceControlAssetPathBatchSchema,
1113
+ requestId: requestIdSchema,
1114
+ type: z.literal(BRIDGE_GET_SOURCE_CONTROL_STATUS_REQUEST_TYPE),
1115
+ version: z.literal(2)
1116
+ });
1117
+ const bridgeRefreshSourceControlStatusRequestSchema = z.object({
1118
+ assetPaths: sourceControlAssetPathBatchSchema,
1119
+ requestId: requestIdSchema,
1120
+ type: z.literal(BRIDGE_REFRESH_SOURCE_CONTROL_STATUS_REQUEST_TYPE),
1121
+ version: z.literal(2)
1122
+ });
1123
+ const bridgeCheckoutAssetsRequestSchema = z.object({
1124
+ assetPaths: sourceControlAssetPathBatchSchema,
1125
+ requestId: requestIdSchema,
1126
+ type: z.literal(BRIDGE_CHECKOUT_ASSETS_REQUEST_TYPE),
1127
+ version: z.literal(2)
1128
+ });
1129
+ const bridgeGetSourceControlJobStatusRequestSchema = z.object({
1130
+ requestId: requestIdSchema,
1131
+ sourceControlJobId: sourceControlJobIdSchema,
1132
+ type: z.literal(BRIDGE_GET_SOURCE_CONTROL_JOB_STATUS_REQUEST_TYPE),
1133
+ version: z.literal(2)
1134
+ });
975
1135
  const bridgeGetAssetDependenciesRequestSchema = z.object({
976
1136
  assetPath: assetPathSchema,
977
1137
  categories: z.array(assetDependencyCategorySchema).max(BRIDGE_ASSET_DEPENDENCY_CATEGORIES.length).default(["package"]),
@@ -1714,6 +1874,211 @@ const bridgeGetOpenLevelStatusSuccessResponseSchema = z.object({
1714
1874
  path: ["finishedAt"]
1715
1875
  });
1716
1876
  });
1877
+ const bridgeDataTableColumnSchema = z.object({
1878
+ name: z.string(),
1879
+ type: z.string()
1880
+ });
1881
+ const bridgeDataTableRowSchema = z.object({
1882
+ rowName: z.string(),
1883
+ values: z.record(z.string(), z.string())
1884
+ });
1885
+ const bridgeReadDataTableSuccessResponseSchema = z.object({
1886
+ assetPath: z.string(),
1887
+ columns: z.array(bridgeDataTableColumnSchema).max(200),
1888
+ columnsTruncated: z.boolean(),
1889
+ nextCursor: dataTableCursorSchema.optional(),
1890
+ ok: z.literal(true),
1891
+ requestId: requestIdSchema,
1892
+ rows: z.array(bridgeDataTableRowSchema).max(100),
1893
+ rowStructName: z.string(),
1894
+ rowStructPath: z.string(),
1895
+ totalRowCount: z.number().int().min(0),
1896
+ truncated: z.boolean(),
1897
+ type: z.literal(BRIDGE_READ_DATA_TABLE_REQUEST_TYPE),
1898
+ valueTruncated: z.boolean(),
1899
+ version: z.literal(2)
1900
+ });
1901
+ const bridgeWriteDataTableRowSuccessResponseSchema = z.object({
1902
+ applied: dataTableRowOperationSchema,
1903
+ assetPath: z.string(),
1904
+ created: z.boolean(),
1905
+ dryRun: z.boolean(),
1906
+ message: z.string().trim().min(1).optional(),
1907
+ ok: z.literal(true),
1908
+ packageDirty: z.boolean(),
1909
+ requestId: requestIdSchema,
1910
+ rowExisted: z.boolean(),
1911
+ rowName: z.string(),
1912
+ state: dataTableWriteStateSchema,
1913
+ transactionTitle: z.string().trim().min(1).optional(),
1914
+ type: z.literal(BRIDGE_WRITE_DATA_TABLE_ROW_REQUEST_TYPE),
1915
+ version: z.literal(2)
1916
+ }).refine(isLevelEditStateConsistent, levelEditStateRefinement);
1917
+ const bridgeExportDataTableSuccessResponseSchema = z.object({
1918
+ assetPath: z.string(),
1919
+ content: z.string().max(BRIDGE_DATA_TABLE_MAX_EXPORT_CHARS),
1920
+ format: dataTableFormatSchema,
1921
+ nextCursor: dataTableCursorSchema.optional(),
1922
+ ok: z.literal(true),
1923
+ requestId: requestIdSchema,
1924
+ rowCount: z.number().int().min(0),
1925
+ totalChars: z.number().int().min(0),
1926
+ truncated: z.boolean(),
1927
+ type: z.literal(BRIDGE_EXPORT_DATA_TABLE_REQUEST_TYPE),
1928
+ version: z.literal(2)
1929
+ });
1930
+ const bridgeImportDataTableSuccessResponseSchema = z.object({
1931
+ assetPath: z.string(),
1932
+ dryRun: z.boolean(),
1933
+ message: z.string().trim().min(1).optional(),
1934
+ ok: z.literal(true),
1935
+ packageDirty: z.boolean(),
1936
+ problemCount: z.number().int().min(0),
1937
+ problems: z.array(z.string()).max(50),
1938
+ replacedRowCount: z.number().int().min(0),
1939
+ requestId: requestIdSchema,
1940
+ rowCount: z.number().int().min(0),
1941
+ state: dataTableWriteStateSchema,
1942
+ transactionTitle: z.string().trim().min(1).optional(),
1943
+ type: z.literal(BRIDGE_IMPORT_DATA_TABLE_REQUEST_TYPE),
1944
+ version: z.literal(2)
1945
+ }).refine(isLevelEditStateConsistent, levelEditStateRefinement);
1946
+ const bridgeCreateDataTableSuccessResponseSchema = z.object({
1947
+ assetPath: z.string(),
1948
+ created: z.boolean(),
1949
+ dryRun: z.boolean(),
1950
+ ok: z.literal(true),
1951
+ packageDirty: z.boolean(),
1952
+ requestId: requestIdSchema,
1953
+ rowStructPath: z.string(),
1954
+ state: dataTableWriteStateSchema,
1955
+ type: z.literal(BRIDGE_CREATE_DATA_TABLE_REQUEST_TYPE),
1956
+ version: z.literal(2)
1957
+ }).refine(isLevelEditStateConsistent, levelEditStateRefinement);
1958
+ const bridgeRenameDataTableSuccessResponseSchema = z.object({
1959
+ assetPath: z.string(),
1960
+ dryRun: z.boolean(),
1961
+ leftRedirector: z.boolean().optional(),
1962
+ newAssetPath: z.string(),
1963
+ ok: z.literal(true),
1964
+ packageDirty: z.boolean(),
1965
+ renamed: z.boolean(),
1966
+ requestId: requestIdSchema,
1967
+ state: dataTableWriteStateSchema,
1968
+ type: z.literal(BRIDGE_RENAME_DATA_TABLE_REQUEST_TYPE),
1969
+ version: z.literal(2)
1970
+ }).refine(isLevelEditStateConsistent, levelEditStateRefinement);
1971
+ const bridgeDeleteDataTableSuccessResponseSchema = z.object({
1972
+ assetPath: z.string(),
1973
+ deleted: z.boolean(),
1974
+ dryRun: z.boolean(),
1975
+ forced: z.boolean(),
1976
+ ok: z.literal(true),
1977
+ referencerCount: z.number().int().min(0),
1978
+ referencers: z.array(z.string()).max(25),
1979
+ requestId: requestIdSchema,
1980
+ state: dataTableWriteStateSchema,
1981
+ type: z.literal(BRIDGE_DELETE_DATA_TABLE_REQUEST_TYPE),
1982
+ version: z.literal(2)
1983
+ }).refine(isLevelEditStateConsistent, levelEditStateRefinement);
1984
+ const bridgeRenameDataTableRowSuccessResponseSchema = z.object({
1985
+ assetPath: z.string(),
1986
+ dryRun: z.boolean(),
1987
+ newRowName: z.string(),
1988
+ ok: z.literal(true),
1989
+ packageDirty: z.boolean(),
1990
+ renamed: z.boolean(),
1991
+ requestId: requestIdSchema,
1992
+ rowName: z.string(),
1993
+ state: dataTableWriteStateSchema,
1994
+ transactionTitle: z.string().trim().min(1).optional(),
1995
+ type: z.literal(BRIDGE_RENAME_DATA_TABLE_ROW_REQUEST_TYPE),
1996
+ version: z.literal(2)
1997
+ }).refine(isLevelEditStateConsistent, levelEditStateRefinement);
1998
+ const bridgeSourceControlFileStateSchema = z.object({
1999
+ assetPath: z.string(),
2000
+ checkedOutBy: z.string().trim().min(1).optional(),
2001
+ error: z.string().optional(),
2002
+ index: z.number().int().min(0),
2003
+ state: sourceControlFileStateSchema
2004
+ });
2005
+ const bridgeGetSourceControlStatusSuccessResponseSchema = z.object({
2006
+ available: z.boolean(),
2007
+ enabled: z.boolean(),
2008
+ files: z.array(bridgeSourceControlFileStateSchema).max(64),
2009
+ message: nonEmptyStringSchema.optional(),
2010
+ ok: z.literal(true),
2011
+ provider: z.string().trim().min(1),
2012
+ requestId: requestIdSchema,
2013
+ state: sourceControlStatusStateSchema,
2014
+ type: z.literal(BRIDGE_GET_SOURCE_CONTROL_STATUS_REQUEST_TYPE),
2015
+ version: z.literal(2)
2016
+ }).refine((value) => value.state === "reported" || value.files.length === 0, {
2017
+ message: "An unsupported get_source_control_status response must carry no file entries.",
2018
+ path: ["files"]
2019
+ });
2020
+ const sourceControlJobAcceptFields = {
2021
+ message: nonEmptyStringSchema.optional(),
2022
+ ok: z.literal(true),
2023
+ requestId: requestIdSchema,
2024
+ sourceControlJobId: sourceControlJobIdSchema.optional(),
2025
+ state: sourceControlJobAcceptStateSchema,
2026
+ version: z.literal(2)
2027
+ };
2028
+ const sourceControlJobAcceptRefinement = {
2029
+ message: "A source control response must carry sourceControlJobId exactly when state is 'accepted'.",
2030
+ path: ["sourceControlJobId"]
2031
+ };
2032
+ function isSourceControlJobAcceptConsistent(value) {
2033
+ return value.state === "accepted" === (value.sourceControlJobId !== void 0);
2034
+ }
2035
+ const bridgeRefreshSourceControlStatusSuccessResponseSchema = z.object({
2036
+ ...sourceControlJobAcceptFields,
2037
+ type: z.literal(BRIDGE_REFRESH_SOURCE_CONTROL_STATUS_REQUEST_TYPE)
2038
+ }).refine(isSourceControlJobAcceptConsistent, sourceControlJobAcceptRefinement);
2039
+ const bridgeCheckoutAssetsSuccessResponseSchema = z.object({
2040
+ ...sourceControlJobAcceptFields,
2041
+ type: z.literal(BRIDGE_CHECKOUT_ASSETS_REQUEST_TYPE)
2042
+ }).refine(isSourceControlJobAcceptConsistent, sourceControlJobAcceptRefinement);
2043
+ const bridgeSourceControlJobItemResultSchema = z.object({
2044
+ assetPath: z.string(),
2045
+ error: z.string().optional(),
2046
+ index: z.number().int().min(0),
2047
+ ok: z.boolean(),
2048
+ state: sourceControlFileStateSchema
2049
+ });
2050
+ const bridgeGetSourceControlJobStatusSuccessResponseSchema = z.object({
2051
+ durationMs: z.number().int().min(0).optional(),
2052
+ failureCount: z.number().int().min(0),
2053
+ finishedAt: nonEmptyStringSchema.optional(),
2054
+ message: nonEmptyStringSchema.optional(),
2055
+ ok: z.literal(true),
2056
+ operation: sourceControlOperationSchema.optional(),
2057
+ requestId: requestIdSchema,
2058
+ results: z.array(bridgeSourceControlJobItemResultSchema).max(64),
2059
+ sourceControlJobId: sourceControlJobIdSchema,
2060
+ state: sourceControlJobStateSchema,
2061
+ successCount: z.number().int().min(0),
2062
+ type: z.literal(BRIDGE_GET_SOURCE_CONTROL_JOB_STATUS_REQUEST_TYPE),
2063
+ version: z.literal(2)
2064
+ }).superRefine((value, ctx) => {
2065
+ if (value.state === "succeeded" || value.state === "failed") return;
2066
+ if (value.durationMs !== void 0) ctx.addIssue({
2067
+ code: z.ZodIssueCode.custom,
2068
+ message: "durationMs must be absent until the job finishes",
2069
+ path: ["durationMs"]
2070
+ });
2071
+ if (value.finishedAt !== void 0) ctx.addIssue({
2072
+ code: z.ZodIssueCode.custom,
2073
+ message: "finishedAt must be absent until the job finishes",
2074
+ path: ["finishedAt"]
2075
+ });
2076
+ if (value.results.length > 0) ctx.addIssue({
2077
+ code: z.ZodIssueCode.custom,
2078
+ message: "results must be empty until the job finishes",
2079
+ path: ["results"]
2080
+ });
2081
+ });
1717
2082
  const bridgeAssetDependencySchema = z.object({
1718
2083
  category: assetDependencyCategorySchema,
1719
2084
  isHard: z.boolean(),
@@ -1991,6 +2356,18 @@ const bridgeUndoResponseSchema = z.union([bridgeUndoSuccessResponseSchema, bridg
1991
2356
  const bridgeRedoResponseSchema = z.union([bridgeRedoSuccessResponseSchema, bridgeErrorResponseSchema]);
1992
2357
  const bridgeOpenLevelResponseSchema = z.union([bridgeOpenLevelSuccessResponseSchema, bridgeErrorResponseSchema]);
1993
2358
  const bridgeGetOpenLevelStatusResponseSchema = z.union([bridgeGetOpenLevelStatusSuccessResponseSchema, bridgeErrorResponseSchema]);
2359
+ const bridgeReadDataTableResponseSchema = z.union([bridgeReadDataTableSuccessResponseSchema, bridgeErrorResponseSchema]);
2360
+ const bridgeWriteDataTableRowResponseSchema = z.union([bridgeWriteDataTableRowSuccessResponseSchema, bridgeErrorResponseSchema]);
2361
+ const bridgeExportDataTableResponseSchema = z.union([bridgeExportDataTableSuccessResponseSchema, bridgeErrorResponseSchema]);
2362
+ const bridgeImportDataTableResponseSchema = z.union([bridgeImportDataTableSuccessResponseSchema, bridgeErrorResponseSchema]);
2363
+ const bridgeCreateDataTableResponseSchema = z.union([bridgeCreateDataTableSuccessResponseSchema, bridgeErrorResponseSchema]);
2364
+ const bridgeRenameDataTableResponseSchema = z.union([bridgeRenameDataTableSuccessResponseSchema, bridgeErrorResponseSchema]);
2365
+ const bridgeDeleteDataTableResponseSchema = z.union([bridgeDeleteDataTableSuccessResponseSchema, bridgeErrorResponseSchema]);
2366
+ const bridgeRenameDataTableRowResponseSchema = z.union([bridgeRenameDataTableRowSuccessResponseSchema, bridgeErrorResponseSchema]);
2367
+ const bridgeGetSourceControlStatusResponseSchema = z.union([bridgeGetSourceControlStatusSuccessResponseSchema, bridgeErrorResponseSchema]);
2368
+ const bridgeRefreshSourceControlStatusResponseSchema = z.union([bridgeRefreshSourceControlStatusSuccessResponseSchema, bridgeErrorResponseSchema]);
2369
+ const bridgeCheckoutAssetsResponseSchema = z.union([bridgeCheckoutAssetsSuccessResponseSchema, bridgeErrorResponseSchema]);
2370
+ const bridgeGetSourceControlJobStatusResponseSchema = z.union([bridgeGetSourceControlJobStatusSuccessResponseSchema, bridgeErrorResponseSchema]);
1994
2371
  const bridgeFindAssetsResponseSchema = z.union([bridgeFindAssetsSuccessResponseSchema, bridgeErrorResponseSchema]);
1995
2372
  const bridgeGetAssetDependenciesResponseSchema = z.union([bridgeGetAssetDependenciesSuccessResponseSchema, bridgeErrorResponseSchema]);
1996
2373
  const bridgeFindBlueprintReferencesResponseSchema = z.union([bridgeFindBlueprintReferencesSuccessResponseSchema, bridgeErrorResponseSchema]);
@@ -2002,6 +2379,126 @@ const bridgeGetBlueprintCompileStatusResponseSchema = z.union([bridgeGetBlueprin
2002
2379
  const bridgeGetConfigValueResponseSchema = z.union([bridgeGetConfigValueSuccessResponseSchema, bridgeErrorResponseSchema]);
2003
2380
  const bridgeSetConfigValueResponseSchema = z.union([bridgeSetConfigValueSuccessResponseSchema, bridgeErrorResponseSchema]);
2004
2381
  const bridgeListConfigSectionResponseSchema = z.union([bridgeListConfigSectionSuccessResponseSchema, bridgeErrorResponseSchema]);
2382
+ function createBridgeReadDataTableRequest(assetPath, options = {}, requestId = randomUUID()) {
2383
+ return bridgeReadDataTableRequestSchema.parse({
2384
+ assetPath,
2385
+ cursor: options.cursor,
2386
+ loadIfNeeded: options.loadIfNeeded,
2387
+ maxRows: options.maxRows ?? 25,
2388
+ requestId,
2389
+ rowNameFilter: options.rowNameFilter,
2390
+ type: BRIDGE_READ_DATA_TABLE_REQUEST_TYPE,
2391
+ version: 2
2392
+ });
2393
+ }
2394
+ function createBridgeWriteDataTableRowRequest(assetPath, rowName, operation, options = {}, requestId = randomUUID()) {
2395
+ return bridgeWriteDataTableRowRequestSchema.parse({
2396
+ assetPath,
2397
+ dryRun: options.dryRun,
2398
+ operation,
2399
+ requestId,
2400
+ rowName,
2401
+ type: BRIDGE_WRITE_DATA_TABLE_ROW_REQUEST_TYPE,
2402
+ values: options.values,
2403
+ version: 2
2404
+ });
2405
+ }
2406
+ function createBridgeExportDataTableRequest(assetPath, options = {}, requestId = randomUUID()) {
2407
+ return bridgeExportDataTableRequestSchema.parse({
2408
+ assetPath,
2409
+ cursor: options.cursor,
2410
+ format: options.format,
2411
+ loadIfNeeded: options.loadIfNeeded,
2412
+ requestId,
2413
+ type: BRIDGE_EXPORT_DATA_TABLE_REQUEST_TYPE,
2414
+ version: 2
2415
+ });
2416
+ }
2417
+ function createBridgeImportDataTableRequest(assetPath, content, options = {}, requestId = randomUUID()) {
2418
+ return bridgeImportDataTableRequestSchema.parse({
2419
+ assetPath,
2420
+ content,
2421
+ dryRun: options.dryRun,
2422
+ format: options.format,
2423
+ requestId,
2424
+ type: BRIDGE_IMPORT_DATA_TABLE_REQUEST_TYPE,
2425
+ version: 2
2426
+ });
2427
+ }
2428
+ function createBridgeCreateDataTableRequest(assetPath, rowStructPath, options = {}, requestId = randomUUID()) {
2429
+ return bridgeCreateDataTableRequestSchema.parse({
2430
+ assetPath,
2431
+ dryRun: options.dryRun,
2432
+ requestId,
2433
+ rowStructPath,
2434
+ type: BRIDGE_CREATE_DATA_TABLE_REQUEST_TYPE,
2435
+ version: 2
2436
+ });
2437
+ }
2438
+ function createBridgeRenameDataTableRequest(assetPath, options = {}, requestId = randomUUID()) {
2439
+ return bridgeRenameDataTableRequestSchema.parse({
2440
+ assetPath,
2441
+ destinationPath: options.destinationPath,
2442
+ dryRun: options.dryRun,
2443
+ newName: options.newName,
2444
+ requestId,
2445
+ type: BRIDGE_RENAME_DATA_TABLE_REQUEST_TYPE,
2446
+ version: 2
2447
+ });
2448
+ }
2449
+ function createBridgeDeleteDataTableRequest(assetPath, options = {}, requestId = randomUUID()) {
2450
+ return bridgeDeleteDataTableRequestSchema.parse({
2451
+ assetPath,
2452
+ dryRun: options.dryRun,
2453
+ force: options.force,
2454
+ requestId,
2455
+ type: BRIDGE_DELETE_DATA_TABLE_REQUEST_TYPE,
2456
+ version: 2
2457
+ });
2458
+ }
2459
+ function createBridgeRenameDataTableRowRequest(assetPath, rowName, newRowName, options = {}, requestId = randomUUID()) {
2460
+ return bridgeRenameDataTableRowRequestSchema.parse({
2461
+ assetPath,
2462
+ dryRun: options.dryRun,
2463
+ newRowName,
2464
+ requestId,
2465
+ rowName,
2466
+ type: BRIDGE_RENAME_DATA_TABLE_ROW_REQUEST_TYPE,
2467
+ version: 2
2468
+ });
2469
+ }
2470
+ function createBridgeGetSourceControlStatusRequest(assetPaths, requestId = randomUUID()) {
2471
+ return bridgeGetSourceControlStatusRequestSchema.parse({
2472
+ assetPaths,
2473
+ requestId,
2474
+ type: BRIDGE_GET_SOURCE_CONTROL_STATUS_REQUEST_TYPE,
2475
+ version: 2
2476
+ });
2477
+ }
2478
+ function createBridgeRefreshSourceControlStatusRequest(assetPaths, requestId = randomUUID()) {
2479
+ return bridgeRefreshSourceControlStatusRequestSchema.parse({
2480
+ assetPaths,
2481
+ requestId,
2482
+ type: BRIDGE_REFRESH_SOURCE_CONTROL_STATUS_REQUEST_TYPE,
2483
+ version: 2
2484
+ });
2485
+ }
2486
+ function createBridgeCheckoutAssetsRequest(assetPaths, requestId = randomUUID()) {
2487
+ return bridgeCheckoutAssetsRequestSchema.parse({
2488
+ assetPaths,
2489
+ requestId,
2490
+ type: BRIDGE_CHECKOUT_ASSETS_REQUEST_TYPE,
2491
+ version: 2
2492
+ });
2493
+ }
2494
+ function createBridgeGetSourceControlJobStatusRequest(sourceControlJobId, requestId = randomUUID()) {
2495
+ return bridgeGetSourceControlJobStatusRequestSchema.parse({
2496
+ requestId,
2497
+ sourceControlJobId,
2498
+ type: BRIDGE_GET_SOURCE_CONTROL_JOB_STATUS_REQUEST_TYPE,
2499
+ version: 2
2500
+ });
2501
+ }
2005
2502
  function createBridgeFindAssetsRequest(options = {}, requestId = randomUUID()) {
2006
2503
  return bridgeFindAssetsRequestSchema.parse({
2007
2504
  classNames: options.classNames,
@@ -2569,6 +3066,30 @@ async function sendBridgeStartTraceRequest(request, options) {
2569
3066
  async function sendBridgeStopTraceRequest(request, options) {
2570
3067
  return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeStopTraceResponseSchema, request.requestId);
2571
3068
  }
3069
+ async function sendBridgeReadDataTableRequest(request, options) {
3070
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeReadDataTableResponseSchema, request.requestId);
3071
+ }
3072
+ async function sendBridgeWriteDataTableRowRequest(request, options) {
3073
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeWriteDataTableRowResponseSchema, request.requestId);
3074
+ }
3075
+ async function sendBridgeExportDataTableRequest(request, options) {
3076
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeExportDataTableResponseSchema, request.requestId);
3077
+ }
3078
+ async function sendBridgeImportDataTableRequest(request, options) {
3079
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeImportDataTableResponseSchema, request.requestId);
3080
+ }
3081
+ async function sendBridgeCreateDataTableRequest(request, options) {
3082
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeCreateDataTableResponseSchema, request.requestId);
3083
+ }
3084
+ async function sendBridgeRenameDataTableRequest(request, options) {
3085
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeRenameDataTableResponseSchema, request.requestId);
3086
+ }
3087
+ async function sendBridgeDeleteDataTableRequest(request, options) {
3088
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeDeleteDataTableResponseSchema, request.requestId);
3089
+ }
3090
+ async function sendBridgeRenameDataTableRowRequest(request, options) {
3091
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeRenameDataTableRowResponseSchema, request.requestId);
3092
+ }
2572
3093
  async function sendBridgeFindAssetsRequest(request, options) {
2573
3094
  return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeFindAssetsResponseSchema, request.requestId);
2574
3095
  }
@@ -2608,6 +3129,18 @@ async function sendBridgeOpenLevelRequest(request, options) {
2608
3129
  async function sendBridgeGetOpenLevelStatusRequest(request, options) {
2609
3130
  return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeGetOpenLevelStatusResponseSchema, request.requestId);
2610
3131
  }
3132
+ async function sendBridgeGetSourceControlStatusRequest(request, options) {
3133
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeGetSourceControlStatusResponseSchema, request.requestId);
3134
+ }
3135
+ async function sendBridgeRefreshSourceControlStatusRequest(request, options) {
3136
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeRefreshSourceControlStatusResponseSchema, request.requestId);
3137
+ }
3138
+ async function sendBridgeCheckoutAssetsRequest(request, options) {
3139
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeCheckoutAssetsResponseSchema, request.requestId);
3140
+ }
3141
+ async function sendBridgeGetSourceControlJobStatusRequest(request, options) {
3142
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeGetSourceControlJobStatusResponseSchema, request.requestId);
3143
+ }
2611
3144
  async function sendBridgeGetAssetDependenciesRequest(request, options) {
2612
3145
  return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeGetAssetDependenciesResponseSchema, request.requestId);
2613
3146
  }
@@ -4915,6 +5448,7 @@ const WAIT_FOR_JOB_KINDS = [
4915
5448
  "compile",
4916
5449
  "create_class",
4917
5450
  "pie",
5451
+ "source_control",
4918
5452
  "test_run"
4919
5453
  ];
4920
5454
  const WAIT_FOR_JOB_MAX_TIMEOUT_MS = 6e5;
@@ -4922,6 +5456,7 @@ const WAIT_FOR_JOB_DEFAULT_TIMEOUT_MS = {
4922
5456
  compile: 12e4,
4923
5457
  create_class: 6e4,
4924
5458
  pie: 3e4,
5459
+ source_control: 6e4,
4925
5460
  test_run: 3e5
4926
5461
  };
4927
5462
  const WAIT_FOR_JOB_POLL_CADENCE_MS = {
@@ -4937,6 +5472,10 @@ const WAIT_FOR_JOB_POLL_CADENCE_MS = {
4937
5472
  capMs: 2e3,
4938
5473
  initialMs: 250
4939
5474
  },
5475
+ source_control: {
5476
+ capMs: 2e3,
5477
+ initialMs: 250
5478
+ },
4940
5479
  test_run: {
4941
5480
  capMs: 5e3,
4942
5481
  initialMs: 1e3
@@ -5044,7 +5583,7 @@ const waitForJobInputSchema = {
5044
5583
  jobId: z.string().trim().min(1).max(512).describe("Job identifier to wait on: compileJobId (compile), createClassJobId (create_class), testRunId (test_run), or pieSessionId (pie)."),
5045
5584
  kind: z.enum(WAIT_FOR_JOB_KINDS).describe("Which async job the id belongs to. Required — job ids are bare GUIDs and carry no type tag, so the kind cannot be inferred."),
5046
5585
  projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor owns the job."),
5047
- timeoutMs: z.number().int().min(1).optional().describe(`Wall-clock budget for the wait, clamped to ${String(WAIT_FOR_JOB_MAX_TIMEOUT_MS)}ms. Defaults per kind: compile ${String(WAIT_FOR_JOB_DEFAULT_TIMEOUT_MS.compile)}ms, create_class ${String(WAIT_FOR_JOB_DEFAULT_TIMEOUT_MS.create_class)}ms, pie ${String(WAIT_FOR_JOB_DEFAULT_TIMEOUT_MS.pie)}ms, test_run ${String(WAIT_FOR_JOB_DEFAULT_TIMEOUT_MS.test_run)}ms.`)
5586
+ timeoutMs: z.number().int().min(1).optional().describe(`Wall-clock budget for the wait, clamped to ${String(WAIT_FOR_JOB_MAX_TIMEOUT_MS)}ms. Defaults per kind: compile ${String(WAIT_FOR_JOB_DEFAULT_TIMEOUT_MS.compile)}ms, create_class ${String(WAIT_FOR_JOB_DEFAULT_TIMEOUT_MS.create_class)}ms, pie ${String(WAIT_FOR_JOB_DEFAULT_TIMEOUT_MS.pie)}ms, source_control ${String(WAIT_FOR_JOB_DEFAULT_TIMEOUT_MS.source_control)}ms, test_run ${String(WAIT_FOR_JOB_DEFAULT_TIMEOUT_MS.test_run)}ms.`)
5048
5587
  };
5049
5588
  const getEditorStateInputSchema = { projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor should report its state.") };
5050
5589
  const saveDirtyPackagesInputSchema = {
@@ -5747,6 +6286,62 @@ const findAssetsInputSchema = {
5747
6286
  recursiveClasses: bridgeFindAssetsRequestSchema.shape.recursiveClasses.describe("Whether classNames also match subclasses (default true)."),
5748
6287
  recursivePaths: bridgeFindAssetsRequestSchema.shape.recursivePaths.describe("Whether packagePaths also match sub-folders (default true).")
5749
6288
  };
6289
+ const readDataTableInputSchema = {
6290
+ assetPath: bridgeReadDataTableRequestSchema.shape.assetPath.describe("Object path of the DataTable asset, as find_assets reports it (e.g. /Game/Data/DT_Weapons.DT_Weapons)."),
6291
+ cursor: bridgeReadDataTableRequestSchema.shape.cursor.describe("Offset into the name-ordered row list (default 0). Pass the previous response nextCursor to fetch the next page."),
6292
+ loadIfNeeded: bridgeReadDataTableRequestSchema.shape.loadIfNeeded.describe("Whether to load the asset when the editor does not already have it in memory (default true). A DataTable carries no Asset Registry tags describing its rows, so with this false an unloaded table reports asset_not_loaded rather than an empty table."),
6293
+ maxRows: bridgeReadDataTableRequestSchema.shape.maxRows.default(25).describe("Maximum number of rows to return per page (1-100, default 25). totalRowCount still reports the unbounded row count."),
6294
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor holds the DataTable."),
6295
+ rowNameFilter: bridgeReadDataTableRequestSchema.shape.rowNameFilter.describe("Optional case-insensitive substring matched against each row name, applied before paging.")
6296
+ };
6297
+ const writeDataTableRowInputSchema = {
6298
+ assetPath: bridgeWriteDataTableRowRequestSchema.shape.assetPath.describe("Object path of the DataTable asset to write (e.g. /Game/Data/DT_Weapons.DT_Weapons)."),
6299
+ dryRun: bridgeWriteDataTableRowRequestSchema.shape.dryRun.describe("When true, resolve the row struct and validate every column and value without opening a transaction or dirtying the package (default false)."),
6300
+ operation: bridgeWriteDataTableRowRequestSchema.shape.operation.describe("'upsert' inserts the row or replaces it if it already exists; 'delete' removes it by name. Deleting a row that is not there is reported through rowExisted=false, not as an error."),
6301
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor holds the DataTable."),
6302
+ rowName: bridgeWriteDataTableRowRequestSchema.shape.rowName.describe("Name of the row to write or delete, as read_data_table reports it."),
6303
+ values: bridgeWriteDataTableRowRequestSchema.shape.values.describe("Column name to value, in the same ExportText format read_data_table emits (required for upsert, forbidden for delete). Every column and value is validated against the row struct before anything is written: an unknown column or a value that will not parse rejects the whole write, so a row is never partially applied. Columns omitted on an upsert of an existing row keep their current value.")
6304
+ };
6305
+ const exportDataTableInputSchema = {
6306
+ assetPath: bridgeExportDataTableRequestSchema.shape.assetPath.describe("Object path of the DataTable asset to export (e.g. /Game/Data/DT_Weapons.DT_Weapons)."),
6307
+ cursor: bridgeExportDataTableRequestSchema.shape.cursor.describe("Character offset into the serialized table (default 0). Pass the previous response nextCursor to fetch the next chunk."),
6308
+ format: bridgeExportDataTableRequestSchema.shape.format.describe("Serialization to use: 'json' (default) or 'csv'. Both come from the engine's own DataTable exporters."),
6309
+ loadIfNeeded: bridgeExportDataTableRequestSchema.shape.loadIfNeeded.describe("Whether to load the asset when the editor does not already have it in memory (default true)."),
6310
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor holds the DataTable.")
6311
+ };
6312
+ const importDataTableInputSchema = {
6313
+ assetPath: bridgeImportDataTableRequestSchema.shape.assetPath.describe("Object path of the DataTable asset to overwrite (e.g. /Game/Data/DT_Weapons.DT_Weapons)."),
6314
+ content: bridgeImportDataTableRequestSchema.shape.content.describe(`Complete serialized table in the chosen format, up to ${String(BRIDGE_DATA_TABLE_MAX_IMPORT_CHARS)} characters — the whole payload travels in one bridge frame, so a table larger than that cannot be imported this way. This REPLACES every row: a row absent from the content is deleted.`),
6315
+ dryRun: bridgeImportDataTableRequestSchema.shape.dryRun.describe("When true, parse the content and report the row count and problems without replacing anything (default false)."),
6316
+ format: bridgeImportDataTableRequestSchema.shape.format.describe("Serialization the content is in: 'json' (default) or 'csv'."),
6317
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor holds the DataTable.")
6318
+ };
6319
+ const createDataTableInputSchema = {
6320
+ assetPath: bridgeCreateDataTableRequestSchema.shape.assetPath.describe("Object path for the new DataTable, e.g. /Game/Data/DT_Weapons. Fails if an asset already exists there."),
6321
+ dryRun: bridgeCreateDataTableRequestSchema.shape.dryRun.describe("When true, resolve and validate the row struct and destination without creating anything (default false)."),
6322
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor should create the table."),
6323
+ rowStructPath: bridgeCreateDataTableRequestSchema.shape.rowStructPath.describe("Struct backing every row. Native structs use /Script/<Module>.<Struct>; a Blueprint struct uses its object path. Must derive FTableRowBase. Cannot be changed after creation.")
6324
+ };
6325
+ const renameDataTableInputSchema = {
6326
+ assetPath: bridgeRenameDataTableRequestSchema.shape.assetPath.describe("Object path of the DataTable to rename or move (e.g. /Game/Data/DT_Weapons.DT_Weapons)."),
6327
+ destinationPath: bridgeRenameDataTableRequestSchema.shape.destinationPath.describe("Package path to move the asset into, e.g. /Game/Data/Archive. Omit to keep it where it is."),
6328
+ dryRun: bridgeRenameDataTableRequestSchema.shape.dryRun.describe("When true, validate the destination without moving anything (default false)."),
6329
+ newName: bridgeRenameDataTableRequestSchema.shape.newName.describe("New asset name without any path, e.g. DT_WeaponsLegacy. Omit to keep the current name."),
6330
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor holds the DataTable.")
6331
+ };
6332
+ const deleteDataTableInputSchema = {
6333
+ assetPath: bridgeDeleteDataTableRequestSchema.shape.assetPath.describe("Object path of the DataTable to delete (e.g. /Game/Data/DT_Weapons.DT_Weapons)."),
6334
+ dryRun: bridgeDeleteDataTableRequestSchema.shape.dryRun.describe("When true, report what would be deleted and what still references it, without deleting (default false)."),
6335
+ force: bridgeDeleteDataTableRequestSchema.shape.force.describe("Delete even when other assets reference the table, breaking those references (default false). Without it a referenced table is refused and its referencers are reported."),
6336
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor holds the DataTable.")
6337
+ };
6338
+ const renameDataTableRowInputSchema = {
6339
+ assetPath: bridgeRenameDataTableRowRequestSchema.shape.assetPath.describe("Object path of the DataTable holding the row (e.g. /Game/Data/DT_Weapons.DT_Weapons)."),
6340
+ dryRun: bridgeRenameDataTableRowRequestSchema.shape.dryRun.describe("When true, validate both names without renaming anything (default false)."),
6341
+ newRowName: bridgeRenameDataTableRowRequestSchema.shape.newRowName.describe("Name to rename the row to. Fails if a row already uses it."),
6342
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor holds the DataTable."),
6343
+ rowName: bridgeRenameDataTableRowRequestSchema.shape.rowName.describe("Current name of the row to rename.")
6344
+ };
5750
6345
  const listActorsInputSchema = {
5751
6346
  classNames: bridgeListActorsRequestSchema.shape.classNames.describe("Actor class names to match, either short (StaticMeshActor, PointLight) or full class paths (/Script/Engine.StaticMeshActor, /Game/BP/BP_Foo.BP_Foo_C). Subclasses match too. Empty means every class."),
5752
6347
  cursor: bridgeListActorsRequestSchema.shape.cursor.describe("Offset into the stable-ordered matched list (default 0). Pass the previous response nextCursor to fetch the next page."),
@@ -5803,6 +6398,22 @@ const getOpenLevelStatusInputSchema = {
5803
6398
  openLevelJobId: bridgeGetOpenLevelStatusRequestSchema.shape.openLevelJobId.describe("openLevelJobId returned by open_level identifying the load job to poll."),
5804
6399
  projectId: projectIdSchema
5805
6400
  };
6401
+ const getSourceControlStatusInputSchema = {
6402
+ assetPaths: bridgeGetSourceControlStatusRequestSchema.shape.assetPaths.describe("Package names or object paths of the assets to report on (1-64), e.g. /Game/Data/DT_Weapons."),
6403
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor should query its source control provider.")
6404
+ };
6405
+ const refreshSourceControlStatusInputSchema = {
6406
+ assetPaths: bridgeRefreshSourceControlStatusRequestSchema.shape.assetPaths.describe("Package names or object paths of the assets whose depot state should be re-read (1-64)."),
6407
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor should refresh its source control state.")
6408
+ };
6409
+ const checkoutAssetsInputSchema = {
6410
+ assetPaths: bridgeCheckoutAssetsRequestSchema.shape.assetPaths.describe("Package names or object paths of the assets to check out (1-64), e.g. /Game/Data/DT_Weapons."),
6411
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor should check the assets out.")
6412
+ };
6413
+ const getSourceControlJobStatusInputSchema = {
6414
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor owns the job."),
6415
+ sourceControlJobId: bridgeGetSourceControlJobStatusRequestSchema.shape.sourceControlJobId.describe("sourceControlJobId returned by checkout_assets or refresh_source_control_status.")
6416
+ };
5806
6417
  const getAssetDependenciesInputSchema = {
5807
6418
  assetPath: bridgeGetAssetDependenciesRequestSchema.shape.assetPath.describe("Package name or object path of the asset to inspect (e.g. /Game/BP/BP_Player or /Game/BP/BP_Player.BP_Player)."),
5808
6419
  categories: bridgeGetAssetDependenciesRequestSchema.shape.categories.describe("Dependency categories to include: 'package' (default), 'searchable_name', 'manage'."),
@@ -6448,6 +7059,46 @@ function registerMcpTools(server, dependencies) {
6448
7059
  projectId
6449
7060
  }, dependencies));
6450
7061
  });
7062
+ server.registerTool("get_source_control_status", {
7063
+ description: "Report the editor's source control provider and the per-file state of up to 64 assets. Answered from the provider's state cache, so it costs no network round trip and returns inline. enabled distinguishes 'no source control configured' from 'configured, and this file is simply not checked out'; per-file state is one of checked_out_by_me, checked_out_by_other (with checkedOutBy naming the holder), marked_for_add, not_checked_out, not_in_depot, unknown. A file the editor has never queried reports 'unknown' — call refresh_source_control_status to fetch its real state from the server. Call this before any asset write on a team project: under Perforce or Git-LFS file locking a .uasset stays read-only until checked out, and the write otherwise fails later at save time.",
7064
+ inputSchema: getSourceControlStatusInputSchema,
7065
+ title: "Get Source Control Status"
7066
+ }, async ({ assetPaths, projectId }) => {
7067
+ return toToolSuccess(await getSourceControlStatus({
7068
+ assetPaths,
7069
+ projectId
7070
+ }, dependencies));
7071
+ });
7072
+ server.registerTool("refresh_source_control_status", {
7073
+ description: "Re-read the depot state of up to 64 assets from the source control server, so a later get_source_control_status reports something better than 'unknown'. This is a network round trip to the provider and runs far past what a bridge request can wait for, so it only accepts the job: it returns state 'accepted' with a sourceControlJobId to poll with get_source_control_job_status (or wait_for_job with kind source_control).",
7074
+ inputSchema: refreshSourceControlStatusInputSchema,
7075
+ title: "Refresh Source Control Status"
7076
+ }, async ({ assetPaths, projectId }) => {
7077
+ return toToolSuccess(await refreshSourceControlStatus({
7078
+ assetPaths,
7079
+ projectId
7080
+ }, dependencies));
7081
+ });
7082
+ server.registerTool("checkout_assets", {
7083
+ description: "Check up to 64 assets out of source control so they become writable. This is a network round trip to the provider and runs far past what a bridge request can wait for, so it only accepts the job: it returns state 'accepted' with a sourceControlJobId to poll with get_source_control_job_status (or wait_for_job with kind source_control). The poll reports per-item results with successCount / failureCount — one asset the provider refuses does not abort the rest. This checks out only; it never submits, and a checked-out asset stays checked out until a human submits or reverts it.",
7084
+ inputSchema: checkoutAssetsInputSchema,
7085
+ title: "Checkout Assets"
7086
+ }, async ({ assetPaths, projectId }) => {
7087
+ return toToolSuccess(await checkoutAssets({
7088
+ assetPaths,
7089
+ projectId
7090
+ }, dependencies));
7091
+ });
7092
+ server.registerTool("get_source_control_job_status", {
7093
+ description: "Poll a source control job started by checkout_assets or refresh_source_control_status for a projectId; returns state (running, succeeded, failed, unknown_job), which operation it was, and once finished the per-file results with successCount / failureCount and timing. Poll until the state leaves 'running' before writing to any asset in the batch.",
7094
+ inputSchema: getSourceControlJobStatusInputSchema,
7095
+ title: "Get Source Control Job Status"
7096
+ }, async ({ projectId, sourceControlJobId }) => {
7097
+ return toToolSuccess(await getSourceControlJobStatus({
7098
+ projectId,
7099
+ sourceControlJobId
7100
+ }, dependencies));
7101
+ });
6451
7102
  server.registerTool("find_assets", {
6452
7103
  description: "Query a project's Unreal Asset Registry for assets by class and/or content path, without loading them; returns each match's name, class, package name, package path, and object path. Requires at least one of classNames or packagePaths — an unfiltered query would stall the editor enumerating every asset. Results are paged: pass the previous response nextCursor to fetch the next page (totalCount reports the full match count). scanning=true means the registry is still indexing, so an empty result does not prove the asset is absent.",
6453
7104
  inputSchema: findAssetsInputSchema,
@@ -6464,6 +7115,110 @@ function registerMcpTools(server, dependencies) {
6464
7115
  recursivePaths
6465
7116
  }, dependencies));
6466
7117
  });
7118
+ server.registerTool("read_data_table", {
7119
+ description: "Read a DataTable asset's contents: the row struct it is typed on, its ordered column list (name plus the same type rendering get_class_reflection emits), and its rows as column-name-to-value maps. Values are ExportText, the format write_data_table_row accepts back, so a value read here round-trips. Rows are paged by name order: pass the previous response nextCursor to fetch the next page (totalRowCount reports the full count). A DataTable is a binary .uasset with no Asset Registry tags describing its rows, so there is no way to read one off disk instead — this tool loads it.",
7120
+ inputSchema: readDataTableInputSchema,
7121
+ title: "Read Data Table"
7122
+ }, async ({ assetPath, cursor, loadIfNeeded, maxRows, projectId, rowNameFilter }) => {
7123
+ return toToolSuccess(await readDataTable({
7124
+ assetPath,
7125
+ cursor,
7126
+ loadIfNeeded,
7127
+ maxRows,
7128
+ projectId,
7129
+ rowNameFilter
7130
+ }, dependencies));
7131
+ });
7132
+ server.registerTool("write_data_table_row", {
7133
+ description: "Insert, update, or delete a single DataTable row. Every column name and value is validated against the row struct before anything is written — an unknown column or a value that will not parse rejects the whole write with an error naming the field, so a row is never partially applied and there is no silent coercion. The write runs inside one transaction, so a human at the editor can Ctrl+Z it, and it marks the package dirty without saving: call save_dirty_packages to persist. Deleting a row that does not exist reports rowExisted=false rather than failing. Use dryRun to validate first. For a whole-table replace use import_data_table instead.",
7134
+ inputSchema: writeDataTableRowInputSchema,
7135
+ title: "Write Data Table Row"
7136
+ }, async ({ assetPath, dryRun, operation, projectId, rowName, values }) => {
7137
+ return toToolSuccess(await writeDataTableRow({
7138
+ assetPath,
7139
+ dryRun,
7140
+ operation,
7141
+ projectId,
7142
+ rowName,
7143
+ values
7144
+ }, dependencies));
7145
+ });
7146
+ server.registerTool("export_data_table", {
7147
+ description: "Serialize a whole DataTable through the engine's own JSON or CSV exporter. Returned in chunks by character offset because the engine produces one string rather than something row-addressable: concatenate the chunks in cursor order to reassemble, and note a single chunk is not valid JSON or CSV on its own. Use read_data_table when you want structured, per-row access; use this when you want the table in the same text form the editor's own export produces.",
7148
+ inputSchema: exportDataTableInputSchema,
7149
+ title: "Export Data Table"
7150
+ }, async ({ assetPath, cursor, format, loadIfNeeded, projectId }) => {
7151
+ return toToolSuccess(await exportDataTable({
7152
+ assetPath,
7153
+ cursor,
7154
+ format,
7155
+ loadIfNeeded,
7156
+ projectId
7157
+ }, dependencies));
7158
+ });
7159
+ server.registerTool("import_data_table", {
7160
+ description: "REPLACE every row of a DataTable from a JSON or CSV payload, using the engine's own importer. This is destructive: any row not present in the content is deleted, so it is the wrong tool for changing one value — use write_data_table_row for that. The whole payload travels in one bridge frame, so a table beyond the content character bound cannot be imported this way. Runs inside one transaction (Ctrl+Z reverses it) and marks the package dirty without saving. Rows the importer rejects come back in problems while the rest still apply, so check problemCount even on success; dryRun reports the row count and problems without replacing anything.",
7161
+ inputSchema: importDataTableInputSchema,
7162
+ title: "Import Data Table"
7163
+ }, async ({ assetPath, content, dryRun, format, projectId }) => {
7164
+ return toToolSuccess(await importDataTable({
7165
+ assetPath,
7166
+ content,
7167
+ dryRun,
7168
+ format,
7169
+ projectId
7170
+ }, dependencies));
7171
+ });
7172
+ server.registerTool("create_data_table", {
7173
+ description: "Create a new empty DataTable asset backed by a row struct. The struct is fixed at creation — UDataTable can have its RowStruct swapped later, but that reinterprets existing rows through the new layout, so the bridge does not expose it; create a new table instead. The struct must derive FTableRowBase, and creation fails if an asset already exists at the path. The new table is marked dirty but not written to disk: call save_dirty_packages to persist it, or it disappears when the editor closes. Use read_data_table to confirm the column set the struct produced.",
7174
+ inputSchema: createDataTableInputSchema,
7175
+ title: "Create Data Table"
7176
+ }, async ({ assetPath, dryRun, projectId, rowStructPath }) => {
7177
+ return toToolSuccess(await createDataTable({
7178
+ assetPath,
7179
+ dryRun,
7180
+ projectId,
7181
+ rowStructPath
7182
+ }, dependencies));
7183
+ });
7184
+ server.registerTool("rename_data_table", {
7185
+ description: "Rename a DataTable, move it to another package path, or both. Goes through the editor's asset tools, so references from other assets are fixed up and a redirector is left at the old path to keep hard references resolving. Renaming is not a transaction the editor can Ctrl+Z — it rewrites packages on disk. Use dryRun to check the destination is free first.",
7186
+ inputSchema: renameDataTableInputSchema,
7187
+ title: "Rename Data Table"
7188
+ }, async ({ assetPath, destinationPath, dryRun, newName, projectId }) => {
7189
+ return toToolSuccess(await renameDataTable({
7190
+ assetPath,
7191
+ destinationPath,
7192
+ dryRun,
7193
+ newName,
7194
+ projectId
7195
+ }, dependencies));
7196
+ });
7197
+ server.registerTool("delete_data_table", {
7198
+ description: "Delete a DataTable asset from the project. This is the one DataTable tool that cannot be undone — asset deletion is not a transaction, so there is no Ctrl+Z and no dirty-package step to reconsider at. A table other assets still reference is refused, and those referencers are reported; force deletes anyway and breaks them. Always dryRun first on a table you did not create: it reports the referencers without touching anything.",
7199
+ inputSchema: deleteDataTableInputSchema,
7200
+ title: "Delete Data Table"
7201
+ }, async ({ assetPath, dryRun, force, projectId }) => {
7202
+ return toToolSuccess(await deleteDataTable({
7203
+ assetPath,
7204
+ dryRun,
7205
+ force,
7206
+ projectId
7207
+ }, dependencies));
7208
+ });
7209
+ server.registerTool("rename_data_table_row", {
7210
+ description: "Rename a single row, keeping its position in the table and its values intact. Not the same as an upsert under the new name followed by a delete of the old one: that pair costs two transactions and moves the row to the end. Fails if the row does not exist or the new name is already taken. Runs inside one transaction (Ctrl+Z reverses it) and marks the package dirty without saving. Note that row names are how other assets address rows, so a rename breaks any FDataTableRowHandle pointing at the old name — nothing fixes those up.",
7211
+ inputSchema: renameDataTableRowInputSchema,
7212
+ title: "Rename Data Table Row"
7213
+ }, async ({ assetPath, dryRun, newRowName, projectId, rowName }) => {
7214
+ return toToolSuccess(await renameDataTableRow({
7215
+ assetPath,
7216
+ dryRun,
7217
+ newRowName,
7218
+ projectId,
7219
+ rowName
7220
+ }, dependencies));
7221
+ });
6467
7222
  server.registerTool("get_asset_dependencies", {
6468
7223
  description: "Report an asset's package-level dependencies (what it needs) and referencers (what needs it) from the Unreal Asset Registry — the Reference Viewer data — so you can judge the blast radius before changing it. Depth is 1: transitive links are not walked. Each list pages independently via nextDependencyCursor / nextReferencerCursor.",
6469
7224
  inputSchema: getAssetDependenciesInputSchema,
@@ -6836,6 +7591,91 @@ async function getOpenLevelStatus(input, dependencies) {
6836
7591
  if (response.durationMs !== void 0) result.durationMs = response.durationMs;
6837
7592
  return result;
6838
7593
  }
7594
+ async function getSourceControlStatus(input, dependencies) {
7595
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7596
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7597
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7598
+ const response = await sendBridgeGetSourceControlStatusRequest(createBridgeGetSourceControlStatusRequest(input.assetPaths), {
7599
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7600
+ host: BRIDGE_LOOPBACK_HOST,
7601
+ port: entry.port,
7602
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7603
+ });
7604
+ if (!response.ok) throw new Error(`Bridge get_source_control_status failed (${response.error.code}): ${response.error.message}`);
7605
+ const result = {
7606
+ available: response.available,
7607
+ enabled: response.enabled,
7608
+ files: response.files,
7609
+ projectId: entry.projectId,
7610
+ provider: response.provider,
7611
+ state: response.state
7612
+ };
7613
+ if (response.message !== void 0) result.message = response.message;
7614
+ return result;
7615
+ }
7616
+ async function refreshSourceControlStatus(input, dependencies) {
7617
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7618
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7619
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7620
+ const request = createBridgeRefreshSourceControlStatusRequest(input.assetPaths);
7621
+ const response = await sendBridgeRefreshSourceControlStatusRequest(request, {
7622
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7623
+ host: BRIDGE_LOOPBACK_HOST,
7624
+ port: entry.port,
7625
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7626
+ });
7627
+ if (!response.ok) throw new Error(`Bridge refresh_source_control_status failed (${response.error.code}): ${response.error.message}`);
7628
+ return buildSourceControlJobAcceptResult(response, request.assetPaths, entry.projectId);
7629
+ }
7630
+ async function checkoutAssets(input, dependencies) {
7631
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7632
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7633
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7634
+ const request = createBridgeCheckoutAssetsRequest(input.assetPaths);
7635
+ const response = await sendBridgeCheckoutAssetsRequest(request, {
7636
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7637
+ host: BRIDGE_LOOPBACK_HOST,
7638
+ port: entry.port,
7639
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7640
+ });
7641
+ if (!response.ok) throw new Error(`Bridge checkout_assets failed (${response.error.code}): ${response.error.message}`);
7642
+ return buildSourceControlJobAcceptResult(response, request.assetPaths, entry.projectId);
7643
+ }
7644
+ function buildSourceControlJobAcceptResult(response, assetPaths, projectId) {
7645
+ const result = {
7646
+ assetPaths,
7647
+ projectId,
7648
+ state: response.state
7649
+ };
7650
+ if (response.sourceControlJobId !== void 0) result.sourceControlJobId = response.sourceControlJobId;
7651
+ if (response.message !== void 0) result.message = response.message;
7652
+ return result;
7653
+ }
7654
+ async function getSourceControlJobStatus(input, dependencies) {
7655
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7656
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7657
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7658
+ const response = await sendBridgeGetSourceControlJobStatusRequest(createBridgeGetSourceControlJobStatusRequest(input.sourceControlJobId), {
7659
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7660
+ host: BRIDGE_LOOPBACK_HOST,
7661
+ port: entry.port,
7662
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7663
+ });
7664
+ if (!response.ok) throw new Error(`Bridge get_source_control_job_status failed (${response.error.code}): ${response.error.message}`);
7665
+ const result = {
7666
+ failureCount: response.failureCount,
7667
+ projectId: entry.projectId,
7668
+ results: response.results,
7669
+ sourceControlJobId: response.sourceControlJobId,
7670
+ state: response.state,
7671
+ successCount: response.successCount
7672
+ };
7673
+ if (response.operation !== void 0) result.operation = response.operation;
7674
+ if (response.message !== void 0) result.message = response.message;
7675
+ if (response.finishedAt !== void 0) result.finishedAt = response.finishedAt;
7676
+ if (response.durationMs !== void 0) result.durationMs = response.durationMs;
7677
+ return result;
7678
+ }
6839
7679
  async function findAssets(input, dependencies) {
6840
7680
  const normalizedProjectId = projectIdSchema.parse(input.projectId);
6841
7681
  const entry = dependencies.registry.getProject(normalizedProjectId);
@@ -6864,6 +7704,213 @@ async function findAssets(input, dependencies) {
6864
7704
  truncated: response.truncated
6865
7705
  };
6866
7706
  }
7707
+ async function readDataTable(input, dependencies) {
7708
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7709
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7710
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7711
+ const response = await sendBridgeReadDataTableRequest(createBridgeReadDataTableRequest(input.assetPath, {
7712
+ cursor: input.cursor,
7713
+ loadIfNeeded: input.loadIfNeeded,
7714
+ maxRows: input.maxRows,
7715
+ rowNameFilter: input.rowNameFilter
7716
+ }), {
7717
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7718
+ host: "127.0.0.1",
7719
+ port: entry.port,
7720
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7721
+ });
7722
+ if (!response.ok) throw new Error(`Bridge read_data_table failed (${response.error.code}): ${response.error.message}`);
7723
+ return {
7724
+ assetPath: response.assetPath,
7725
+ columns: response.columns,
7726
+ columnsTruncated: response.columnsTruncated,
7727
+ nextCursor: response.nextCursor,
7728
+ projectId: entry.projectId,
7729
+ rows: response.rows,
7730
+ rowStructName: response.rowStructName,
7731
+ rowStructPath: response.rowStructPath,
7732
+ totalRowCount: response.totalRowCount,
7733
+ truncated: response.truncated,
7734
+ valueTruncated: response.valueTruncated
7735
+ };
7736
+ }
7737
+ async function writeDataTableRow(input, dependencies) {
7738
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7739
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7740
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7741
+ const response = await sendBridgeWriteDataTableRowRequest(createBridgeWriteDataTableRowRequest(input.assetPath, input.rowName, input.operation, {
7742
+ dryRun: input.dryRun,
7743
+ values: input.values
7744
+ }), {
7745
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7746
+ host: "127.0.0.1",
7747
+ port: entry.port,
7748
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7749
+ });
7750
+ if (!response.ok) throw new Error(`Bridge write_data_table_row failed (${response.error.code}): ${response.error.message}`);
7751
+ return {
7752
+ applied: response.applied,
7753
+ assetPath: response.assetPath,
7754
+ created: response.created,
7755
+ dryRun: response.dryRun,
7756
+ message: response.message,
7757
+ packageDirty: response.packageDirty,
7758
+ projectId: entry.projectId,
7759
+ rowExisted: response.rowExisted,
7760
+ rowName: response.rowName,
7761
+ state: response.state,
7762
+ transactionTitle: response.transactionTitle
7763
+ };
7764
+ }
7765
+ async function exportDataTable(input, dependencies) {
7766
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7767
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7768
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7769
+ const response = await sendBridgeExportDataTableRequest(createBridgeExportDataTableRequest(input.assetPath, {
7770
+ cursor: input.cursor,
7771
+ format: input.format,
7772
+ loadIfNeeded: input.loadIfNeeded
7773
+ }), {
7774
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7775
+ host: "127.0.0.1",
7776
+ port: entry.port,
7777
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7778
+ });
7779
+ if (!response.ok) throw new Error(`Bridge export_data_table failed (${response.error.code}): ${response.error.message}`);
7780
+ return {
7781
+ assetPath: response.assetPath,
7782
+ content: response.content,
7783
+ format: response.format,
7784
+ nextCursor: response.nextCursor,
7785
+ projectId: entry.projectId,
7786
+ rowCount: response.rowCount,
7787
+ totalChars: response.totalChars,
7788
+ truncated: response.truncated
7789
+ };
7790
+ }
7791
+ async function importDataTable(input, dependencies) {
7792
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7793
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7794
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7795
+ const response = await sendBridgeImportDataTableRequest(createBridgeImportDataTableRequest(input.assetPath, input.content, {
7796
+ dryRun: input.dryRun,
7797
+ format: input.format
7798
+ }), {
7799
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7800
+ host: "127.0.0.1",
7801
+ port: entry.port,
7802
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7803
+ });
7804
+ if (!response.ok) throw new Error(`Bridge import_data_table failed (${response.error.code}): ${response.error.message}`);
7805
+ return {
7806
+ assetPath: response.assetPath,
7807
+ dryRun: response.dryRun,
7808
+ message: response.message,
7809
+ packageDirty: response.packageDirty,
7810
+ problemCount: response.problemCount,
7811
+ problems: response.problems,
7812
+ projectId: entry.projectId,
7813
+ replacedRowCount: response.replacedRowCount,
7814
+ rowCount: response.rowCount,
7815
+ state: response.state,
7816
+ transactionTitle: response.transactionTitle
7817
+ };
7818
+ }
7819
+ async function createDataTable(input, dependencies) {
7820
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7821
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7822
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7823
+ const response = await sendBridgeCreateDataTableRequest(createBridgeCreateDataTableRequest(input.assetPath, input.rowStructPath, { dryRun: input.dryRun }), {
7824
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7825
+ host: "127.0.0.1",
7826
+ port: entry.port,
7827
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7828
+ });
7829
+ if (!response.ok) throw new Error(`Bridge create_data_table failed (${response.error.code}): ${response.error.message}`);
7830
+ return {
7831
+ assetPath: response.assetPath,
7832
+ created: response.created,
7833
+ dryRun: response.dryRun,
7834
+ packageDirty: response.packageDirty,
7835
+ projectId: entry.projectId,
7836
+ rowStructPath: response.rowStructPath,
7837
+ state: response.state
7838
+ };
7839
+ }
7840
+ async function renameDataTable(input, dependencies) {
7841
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7842
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7843
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7844
+ const response = await sendBridgeRenameDataTableRequest(createBridgeRenameDataTableRequest(input.assetPath, {
7845
+ destinationPath: input.destinationPath,
7846
+ dryRun: input.dryRun,
7847
+ newName: input.newName
7848
+ }), {
7849
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7850
+ host: "127.0.0.1",
7851
+ port: entry.port,
7852
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7853
+ });
7854
+ if (!response.ok) throw new Error(`Bridge rename_data_table failed (${response.error.code}): ${response.error.message}`);
7855
+ return {
7856
+ assetPath: response.assetPath,
7857
+ dryRun: response.dryRun,
7858
+ leftRedirector: response.leftRedirector,
7859
+ newAssetPath: response.newAssetPath,
7860
+ packageDirty: response.packageDirty,
7861
+ projectId: entry.projectId,
7862
+ renamed: response.renamed,
7863
+ state: response.state
7864
+ };
7865
+ }
7866
+ async function deleteDataTable(input, dependencies) {
7867
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7868
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7869
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7870
+ const response = await sendBridgeDeleteDataTableRequest(createBridgeDeleteDataTableRequest(input.assetPath, {
7871
+ dryRun: input.dryRun,
7872
+ force: input.force
7873
+ }), {
7874
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7875
+ host: "127.0.0.1",
7876
+ port: entry.port,
7877
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7878
+ });
7879
+ if (!response.ok) throw new Error(`Bridge delete_data_table failed (${response.error.code}): ${response.error.message}`);
7880
+ return {
7881
+ assetPath: response.assetPath,
7882
+ deleted: response.deleted,
7883
+ dryRun: response.dryRun,
7884
+ forced: response.forced,
7885
+ projectId: entry.projectId,
7886
+ referencerCount: response.referencerCount,
7887
+ referencers: response.referencers,
7888
+ state: response.state
7889
+ };
7890
+ }
7891
+ async function renameDataTableRow(input, dependencies) {
7892
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7893
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7894
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7895
+ const response = await sendBridgeRenameDataTableRowRequest(createBridgeRenameDataTableRowRequest(input.assetPath, input.rowName, input.newRowName, { dryRun: input.dryRun }), {
7896
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7897
+ host: "127.0.0.1",
7898
+ port: entry.port,
7899
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7900
+ });
7901
+ if (!response.ok) throw new Error(`Bridge rename_data_table_row failed (${response.error.code}): ${response.error.message}`);
7902
+ return {
7903
+ assetPath: response.assetPath,
7904
+ dryRun: response.dryRun,
7905
+ newRowName: response.newRowName,
7906
+ packageDirty: response.packageDirty,
7907
+ projectId: entry.projectId,
7908
+ renamed: response.renamed,
7909
+ rowName: response.rowName,
7910
+ state: response.state,
7911
+ transactionTitle: response.transactionTitle
7912
+ };
7913
+ }
6867
7914
  async function getAssetDependencies(input, dependencies) {
6868
7915
  const normalizedProjectId = projectIdSchema.parse(input.projectId);
6869
7916
  const entry = dependencies.registry.getProject(normalizedProjectId);
@@ -7962,6 +9009,10 @@ async function pollJobStatus(kind, jobId, projectId, dependencies) {
7962
9009
  pieSessionId: jobId,
7963
9010
  projectId
7964
9011
  }, dependencies);
9012
+ case "source_control": return await getSourceControlJobStatus({
9013
+ projectId,
9014
+ sourceControlJobId: jobId
9015
+ }, dependencies);
7965
9016
  case "test_run": return await getTestRunStatus({
7966
9017
  projectId,
7967
9018
  testRunId: jobId
@@ -7973,6 +9024,7 @@ function classifyJobStatus(kind, status) {
7973
9024
  case "compile": return status.state === "compiling" ? "pending" : toTerminalOutcome(status.state === "unknown_job");
7974
9025
  case "create_class": return status.state === "creating" ? "pending" : toTerminalOutcome(status.state === "unknown_job");
7975
9026
  case "pie": return status.state === "starting" ? "pending" : "completed";
9027
+ case "source_control": return status.state === "running" ? "pending" : toTerminalOutcome(status.state === "unknown_job");
7976
9028
  case "test_run": return status.state === "running" ? "pending" : toTerminalOutcome(status.state === "unknown_run");
7977
9029
  }
7978
9030
  }
@@ -8229,7 +9281,7 @@ function isMissingFileError(error) {
8229
9281
  //#endregion
8230
9282
  //#region src/server.ts
8231
9283
  const SERVER_NAME = "ue-mcp-server";
8232
- const SERVER_VERSION = "1.0.0";
9284
+ const SERVER_VERSION = "1.2.0";
8233
9285
  const logger = createModuleLogger("bootstrap");
8234
9286
  const enginePingLogger = createModuleLogger("engine-ping");
8235
9287
  const httpApiLogger = createModuleLogger("http-api");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uemcp/server",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "MCP server for making Unreal Engine and C++ development easier",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",