@uemcp/server 1.0.0 → 1.1.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 +30 -0
  2. package/dist/index.mjs +717 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -182,6 +182,36 @@ This is intended to let an MCP client resolve the canonical Unreal project ident
182
182
  - Actor paths do not survive a load: re-run `list_actors` against the new world rather than reusing paths from the old one
183
183
  - 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
184
 
185
+ ### `read_data_table` / `write_data_table_row`
186
+
187
+ - 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
188
+ - 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
189
+ - 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
190
+ - `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
191
+ - `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
192
+ - 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
193
+ - 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
194
+ - 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
195
+ - 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 and reported as `source_control_checkout_required` rather than surfacing later as an unexplained save error
196
+ - In a non-editor plugin build `read_data_table` fails with `unsupported` and `write_data_table_row` returns `state: 'unsupported'`
197
+
198
+ ### `export_data_table` / `import_data_table`
199
+
200
+ - The bulk half, built on the engine's own JSON and CSV serializers rather than the per-row walk above
201
+ - `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
202
+ - `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
203
+ - 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
204
+ - 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
205
+
206
+ ### `create_data_table` / `rename_data_table` / `delete_data_table` / `rename_data_table_row`
207
+
208
+ - The lifecycle half: the tools above all require a table that already exists, and until these landed nothing could make one over the bridge
209
+ - `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
210
+ - `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
211
+ - `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
212
+ - `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
213
+ - 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
214
+
185
215
  ## MCP Resources
186
216
 
187
217
  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,24 @@ 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;
462
480
  const requestIdSchema = z.string().trim().min(1);
463
481
  const nonEmptyStringSchema = z.string().trim().min(1);
464
482
  const boundedStringSchema = z.string().trim().max(512);
@@ -568,6 +586,15 @@ const openLevelJobIdSchema = z.string().trim().min(1);
568
586
  const levelPathSchema = z.string().trim().min(1).max(512);
569
587
  const actorPathBatchSchema = z.array(actorPathSchema).min(1).max(64);
570
588
  const actorTagArraySchema = z.array(boundedStringSchema).max(16);
589
+ const dataTableCursorSchema = z.number().int().min(0);
590
+ const dataTableMaxRowsSchema = z.number().int().min(1).max(100);
591
+ const dataTableRowNameSchema = z.string().trim().min(1).max(256);
592
+ const dataTableRowOperationSchema = z.enum(BRIDGE_DATA_TABLE_ROW_OPERATIONS);
593
+ const dataTableWriteStateSchema = z.enum(BRIDGE_DATA_TABLE_WRITE_STATES);
594
+ const dataTableFormatSchema = z.enum(BRIDGE_DATA_TABLE_FORMATS);
595
+ 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.` });
596
+ const dataTableImportContentSchema = z.string().min(1).max(BRIDGE_DATA_TABLE_MAX_IMPORT_CHARS);
597
+ 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." });
571
598
  const vectorInputSchema = z.object({
572
599
  x: z.number().default(0),
573
600
  y: z.number().default(0),
@@ -972,6 +999,87 @@ const bridgeGetOpenLevelStatusRequestSchema = z.object({
972
999
  type: z.literal(BRIDGE_GET_OPEN_LEVEL_STATUS_REQUEST_TYPE),
973
1000
  version: z.literal(2)
974
1001
  });
1002
+ const bridgeReadDataTableRequestSchema = z.object({
1003
+ assetPath: assetPathSchema,
1004
+ cursor: dataTableCursorSchema.default(0),
1005
+ loadIfNeeded: z.boolean().default(true),
1006
+ maxRows: dataTableMaxRowsSchema,
1007
+ rowNameFilter: boundedStringSchema.optional(),
1008
+ requestId: requestIdSchema,
1009
+ type: z.literal(BRIDGE_READ_DATA_TABLE_REQUEST_TYPE),
1010
+ version: z.literal(2)
1011
+ });
1012
+ const bridgeWriteDataTableRowRequestSchema = z.object({
1013
+ assetPath: assetPathSchema,
1014
+ dryRun: z.boolean().default(false),
1015
+ operation: dataTableRowOperationSchema,
1016
+ rowName: dataTableRowNameSchema,
1017
+ values: dataTableValueMapSchema.optional(),
1018
+ requestId: requestIdSchema,
1019
+ type: z.literal(BRIDGE_WRITE_DATA_TABLE_ROW_REQUEST_TYPE),
1020
+ version: z.literal(2)
1021
+ }).refine((value) => value.operation !== "upsert" || value.values !== void 0, {
1022
+ message: "A write_data_table_row upsert requires values.",
1023
+ path: ["values"]
1024
+ }).refine((value) => value.operation !== "delete" || value.values === void 0, {
1025
+ message: "A write_data_table_row delete must not carry values.",
1026
+ path: ["values"]
1027
+ });
1028
+ const bridgeExportDataTableRequestSchema = z.object({
1029
+ assetPath: assetPathSchema,
1030
+ cursor: dataTableCursorSchema.default(0),
1031
+ format: dataTableFormatSchema.default("json"),
1032
+ loadIfNeeded: z.boolean().default(true),
1033
+ requestId: requestIdSchema,
1034
+ type: z.literal(BRIDGE_EXPORT_DATA_TABLE_REQUEST_TYPE),
1035
+ version: z.literal(2)
1036
+ });
1037
+ const bridgeImportDataTableRequestSchema = z.object({
1038
+ assetPath: assetPathSchema,
1039
+ content: dataTableImportContentSchema,
1040
+ dryRun: z.boolean().default(false),
1041
+ format: dataTableFormatSchema.default("json"),
1042
+ requestId: requestIdSchema,
1043
+ type: z.literal(BRIDGE_IMPORT_DATA_TABLE_REQUEST_TYPE),
1044
+ version: z.literal(2)
1045
+ });
1046
+ const bridgeCreateDataTableRequestSchema = z.object({
1047
+ assetPath: assetPathSchema,
1048
+ dryRun: z.boolean().default(false),
1049
+ rowStructPath: assetPathSchema,
1050
+ requestId: requestIdSchema,
1051
+ type: z.literal(BRIDGE_CREATE_DATA_TABLE_REQUEST_TYPE),
1052
+ version: z.literal(2)
1053
+ });
1054
+ const bridgeRenameDataTableRequestSchema = z.object({
1055
+ assetPath: assetPathSchema,
1056
+ destinationPath: assetPathSchema.optional(),
1057
+ dryRun: z.boolean().default(false),
1058
+ newName: dataTableAssetNameSchema.optional(),
1059
+ requestId: requestIdSchema,
1060
+ type: z.literal(BRIDGE_RENAME_DATA_TABLE_REQUEST_TYPE),
1061
+ version: z.literal(2)
1062
+ }).refine((value) => value.newName !== void 0 || value.destinationPath !== void 0, {
1063
+ message: "A rename_data_table requires a newName, a destinationPath, or both.",
1064
+ path: ["newName"]
1065
+ });
1066
+ const bridgeDeleteDataTableRequestSchema = z.object({
1067
+ assetPath: assetPathSchema,
1068
+ dryRun: z.boolean().default(false),
1069
+ force: z.boolean().default(false),
1070
+ requestId: requestIdSchema,
1071
+ type: z.literal(BRIDGE_DELETE_DATA_TABLE_REQUEST_TYPE),
1072
+ version: z.literal(2)
1073
+ });
1074
+ const bridgeRenameDataTableRowRequestSchema = z.object({
1075
+ assetPath: assetPathSchema,
1076
+ dryRun: z.boolean().default(false),
1077
+ newRowName: dataTableRowNameSchema,
1078
+ rowName: dataTableRowNameSchema,
1079
+ requestId: requestIdSchema,
1080
+ type: z.literal(BRIDGE_RENAME_DATA_TABLE_ROW_REQUEST_TYPE),
1081
+ version: z.literal(2)
1082
+ });
975
1083
  const bridgeGetAssetDependenciesRequestSchema = z.object({
976
1084
  assetPath: assetPathSchema,
977
1085
  categories: z.array(assetDependencyCategorySchema).max(BRIDGE_ASSET_DEPENDENCY_CATEGORIES.length).default(["package"]),
@@ -1714,6 +1822,127 @@ const bridgeGetOpenLevelStatusSuccessResponseSchema = z.object({
1714
1822
  path: ["finishedAt"]
1715
1823
  });
1716
1824
  });
1825
+ const bridgeDataTableColumnSchema = z.object({
1826
+ name: z.string(),
1827
+ type: z.string()
1828
+ });
1829
+ const bridgeDataTableRowSchema = z.object({
1830
+ rowName: z.string(),
1831
+ values: z.record(z.string(), z.string())
1832
+ });
1833
+ const bridgeReadDataTableSuccessResponseSchema = z.object({
1834
+ assetPath: z.string(),
1835
+ columns: z.array(bridgeDataTableColumnSchema).max(200),
1836
+ columnsTruncated: z.boolean(),
1837
+ nextCursor: dataTableCursorSchema.optional(),
1838
+ ok: z.literal(true),
1839
+ requestId: requestIdSchema,
1840
+ rows: z.array(bridgeDataTableRowSchema).max(100),
1841
+ rowStructName: z.string(),
1842
+ rowStructPath: z.string(),
1843
+ totalRowCount: z.number().int().min(0),
1844
+ truncated: z.boolean(),
1845
+ type: z.literal(BRIDGE_READ_DATA_TABLE_REQUEST_TYPE),
1846
+ valueTruncated: z.boolean(),
1847
+ version: z.literal(2)
1848
+ });
1849
+ const bridgeWriteDataTableRowSuccessResponseSchema = z.object({
1850
+ applied: dataTableRowOperationSchema,
1851
+ assetPath: z.string(),
1852
+ created: z.boolean(),
1853
+ dryRun: z.boolean(),
1854
+ message: z.string().trim().min(1).optional(),
1855
+ ok: z.literal(true),
1856
+ packageDirty: z.boolean(),
1857
+ requestId: requestIdSchema,
1858
+ rowExisted: z.boolean(),
1859
+ rowName: z.string(),
1860
+ state: dataTableWriteStateSchema,
1861
+ transactionTitle: z.string().trim().min(1).optional(),
1862
+ type: z.literal(BRIDGE_WRITE_DATA_TABLE_ROW_REQUEST_TYPE),
1863
+ version: z.literal(2)
1864
+ }).refine(isLevelEditStateConsistent, levelEditStateRefinement);
1865
+ const bridgeExportDataTableSuccessResponseSchema = z.object({
1866
+ assetPath: z.string(),
1867
+ content: z.string().max(BRIDGE_DATA_TABLE_MAX_EXPORT_CHARS),
1868
+ format: dataTableFormatSchema,
1869
+ nextCursor: dataTableCursorSchema.optional(),
1870
+ ok: z.literal(true),
1871
+ requestId: requestIdSchema,
1872
+ rowCount: z.number().int().min(0),
1873
+ totalChars: z.number().int().min(0),
1874
+ truncated: z.boolean(),
1875
+ type: z.literal(BRIDGE_EXPORT_DATA_TABLE_REQUEST_TYPE),
1876
+ version: z.literal(2)
1877
+ });
1878
+ const bridgeImportDataTableSuccessResponseSchema = z.object({
1879
+ assetPath: z.string(),
1880
+ dryRun: z.boolean(),
1881
+ message: z.string().trim().min(1).optional(),
1882
+ ok: z.literal(true),
1883
+ packageDirty: z.boolean(),
1884
+ problemCount: z.number().int().min(0),
1885
+ problems: z.array(z.string()).max(50),
1886
+ replacedRowCount: z.number().int().min(0),
1887
+ requestId: requestIdSchema,
1888
+ rowCount: z.number().int().min(0),
1889
+ state: dataTableWriteStateSchema,
1890
+ transactionTitle: z.string().trim().min(1).optional(),
1891
+ type: z.literal(BRIDGE_IMPORT_DATA_TABLE_REQUEST_TYPE),
1892
+ version: z.literal(2)
1893
+ }).refine(isLevelEditStateConsistent, levelEditStateRefinement);
1894
+ const bridgeCreateDataTableSuccessResponseSchema = z.object({
1895
+ assetPath: z.string(),
1896
+ created: z.boolean(),
1897
+ dryRun: z.boolean(),
1898
+ ok: z.literal(true),
1899
+ packageDirty: z.boolean(),
1900
+ requestId: requestIdSchema,
1901
+ rowStructPath: z.string(),
1902
+ state: dataTableWriteStateSchema,
1903
+ type: z.literal(BRIDGE_CREATE_DATA_TABLE_REQUEST_TYPE),
1904
+ version: z.literal(2)
1905
+ }).refine(isLevelEditStateConsistent, levelEditStateRefinement);
1906
+ const bridgeRenameDataTableSuccessResponseSchema = z.object({
1907
+ assetPath: z.string(),
1908
+ dryRun: z.boolean(),
1909
+ leftRedirector: z.boolean().optional(),
1910
+ newAssetPath: z.string(),
1911
+ ok: z.literal(true),
1912
+ packageDirty: z.boolean(),
1913
+ renamed: z.boolean(),
1914
+ requestId: requestIdSchema,
1915
+ state: dataTableWriteStateSchema,
1916
+ type: z.literal(BRIDGE_RENAME_DATA_TABLE_REQUEST_TYPE),
1917
+ version: z.literal(2)
1918
+ }).refine(isLevelEditStateConsistent, levelEditStateRefinement);
1919
+ const bridgeDeleteDataTableSuccessResponseSchema = z.object({
1920
+ assetPath: z.string(),
1921
+ deleted: z.boolean(),
1922
+ dryRun: z.boolean(),
1923
+ forced: z.boolean(),
1924
+ ok: z.literal(true),
1925
+ referencerCount: z.number().int().min(0),
1926
+ referencers: z.array(z.string()).max(25),
1927
+ requestId: requestIdSchema,
1928
+ state: dataTableWriteStateSchema,
1929
+ type: z.literal(BRIDGE_DELETE_DATA_TABLE_REQUEST_TYPE),
1930
+ version: z.literal(2)
1931
+ }).refine(isLevelEditStateConsistent, levelEditStateRefinement);
1932
+ const bridgeRenameDataTableRowSuccessResponseSchema = z.object({
1933
+ assetPath: z.string(),
1934
+ dryRun: z.boolean(),
1935
+ newRowName: z.string(),
1936
+ ok: z.literal(true),
1937
+ packageDirty: z.boolean(),
1938
+ renamed: z.boolean(),
1939
+ requestId: requestIdSchema,
1940
+ rowName: z.string(),
1941
+ state: dataTableWriteStateSchema,
1942
+ transactionTitle: z.string().trim().min(1).optional(),
1943
+ type: z.literal(BRIDGE_RENAME_DATA_TABLE_ROW_REQUEST_TYPE),
1944
+ version: z.literal(2)
1945
+ }).refine(isLevelEditStateConsistent, levelEditStateRefinement);
1717
1946
  const bridgeAssetDependencySchema = z.object({
1718
1947
  category: assetDependencyCategorySchema,
1719
1948
  isHard: z.boolean(),
@@ -1991,6 +2220,14 @@ const bridgeUndoResponseSchema = z.union([bridgeUndoSuccessResponseSchema, bridg
1991
2220
  const bridgeRedoResponseSchema = z.union([bridgeRedoSuccessResponseSchema, bridgeErrorResponseSchema]);
1992
2221
  const bridgeOpenLevelResponseSchema = z.union([bridgeOpenLevelSuccessResponseSchema, bridgeErrorResponseSchema]);
1993
2222
  const bridgeGetOpenLevelStatusResponseSchema = z.union([bridgeGetOpenLevelStatusSuccessResponseSchema, bridgeErrorResponseSchema]);
2223
+ const bridgeReadDataTableResponseSchema = z.union([bridgeReadDataTableSuccessResponseSchema, bridgeErrorResponseSchema]);
2224
+ const bridgeWriteDataTableRowResponseSchema = z.union([bridgeWriteDataTableRowSuccessResponseSchema, bridgeErrorResponseSchema]);
2225
+ const bridgeExportDataTableResponseSchema = z.union([bridgeExportDataTableSuccessResponseSchema, bridgeErrorResponseSchema]);
2226
+ const bridgeImportDataTableResponseSchema = z.union([bridgeImportDataTableSuccessResponseSchema, bridgeErrorResponseSchema]);
2227
+ const bridgeCreateDataTableResponseSchema = z.union([bridgeCreateDataTableSuccessResponseSchema, bridgeErrorResponseSchema]);
2228
+ const bridgeRenameDataTableResponseSchema = z.union([bridgeRenameDataTableSuccessResponseSchema, bridgeErrorResponseSchema]);
2229
+ const bridgeDeleteDataTableResponseSchema = z.union([bridgeDeleteDataTableSuccessResponseSchema, bridgeErrorResponseSchema]);
2230
+ const bridgeRenameDataTableRowResponseSchema = z.union([bridgeRenameDataTableRowSuccessResponseSchema, bridgeErrorResponseSchema]);
1994
2231
  const bridgeFindAssetsResponseSchema = z.union([bridgeFindAssetsSuccessResponseSchema, bridgeErrorResponseSchema]);
1995
2232
  const bridgeGetAssetDependenciesResponseSchema = z.union([bridgeGetAssetDependenciesSuccessResponseSchema, bridgeErrorResponseSchema]);
1996
2233
  const bridgeFindBlueprintReferencesResponseSchema = z.union([bridgeFindBlueprintReferencesSuccessResponseSchema, bridgeErrorResponseSchema]);
@@ -2002,6 +2239,94 @@ const bridgeGetBlueprintCompileStatusResponseSchema = z.union([bridgeGetBlueprin
2002
2239
  const bridgeGetConfigValueResponseSchema = z.union([bridgeGetConfigValueSuccessResponseSchema, bridgeErrorResponseSchema]);
2003
2240
  const bridgeSetConfigValueResponseSchema = z.union([bridgeSetConfigValueSuccessResponseSchema, bridgeErrorResponseSchema]);
2004
2241
  const bridgeListConfigSectionResponseSchema = z.union([bridgeListConfigSectionSuccessResponseSchema, bridgeErrorResponseSchema]);
2242
+ function createBridgeReadDataTableRequest(assetPath, options = {}, requestId = randomUUID()) {
2243
+ return bridgeReadDataTableRequestSchema.parse({
2244
+ assetPath,
2245
+ cursor: options.cursor,
2246
+ loadIfNeeded: options.loadIfNeeded,
2247
+ maxRows: options.maxRows ?? 25,
2248
+ requestId,
2249
+ rowNameFilter: options.rowNameFilter,
2250
+ type: BRIDGE_READ_DATA_TABLE_REQUEST_TYPE,
2251
+ version: 2
2252
+ });
2253
+ }
2254
+ function createBridgeWriteDataTableRowRequest(assetPath, rowName, operation, options = {}, requestId = randomUUID()) {
2255
+ return bridgeWriteDataTableRowRequestSchema.parse({
2256
+ assetPath,
2257
+ dryRun: options.dryRun,
2258
+ operation,
2259
+ requestId,
2260
+ rowName,
2261
+ type: BRIDGE_WRITE_DATA_TABLE_ROW_REQUEST_TYPE,
2262
+ values: options.values,
2263
+ version: 2
2264
+ });
2265
+ }
2266
+ function createBridgeExportDataTableRequest(assetPath, options = {}, requestId = randomUUID()) {
2267
+ return bridgeExportDataTableRequestSchema.parse({
2268
+ assetPath,
2269
+ cursor: options.cursor,
2270
+ format: options.format,
2271
+ loadIfNeeded: options.loadIfNeeded,
2272
+ requestId,
2273
+ type: BRIDGE_EXPORT_DATA_TABLE_REQUEST_TYPE,
2274
+ version: 2
2275
+ });
2276
+ }
2277
+ function createBridgeImportDataTableRequest(assetPath, content, options = {}, requestId = randomUUID()) {
2278
+ return bridgeImportDataTableRequestSchema.parse({
2279
+ assetPath,
2280
+ content,
2281
+ dryRun: options.dryRun,
2282
+ format: options.format,
2283
+ requestId,
2284
+ type: BRIDGE_IMPORT_DATA_TABLE_REQUEST_TYPE,
2285
+ version: 2
2286
+ });
2287
+ }
2288
+ function createBridgeCreateDataTableRequest(assetPath, rowStructPath, options = {}, requestId = randomUUID()) {
2289
+ return bridgeCreateDataTableRequestSchema.parse({
2290
+ assetPath,
2291
+ dryRun: options.dryRun,
2292
+ requestId,
2293
+ rowStructPath,
2294
+ type: BRIDGE_CREATE_DATA_TABLE_REQUEST_TYPE,
2295
+ version: 2
2296
+ });
2297
+ }
2298
+ function createBridgeRenameDataTableRequest(assetPath, options = {}, requestId = randomUUID()) {
2299
+ return bridgeRenameDataTableRequestSchema.parse({
2300
+ assetPath,
2301
+ destinationPath: options.destinationPath,
2302
+ dryRun: options.dryRun,
2303
+ newName: options.newName,
2304
+ requestId,
2305
+ type: BRIDGE_RENAME_DATA_TABLE_REQUEST_TYPE,
2306
+ version: 2
2307
+ });
2308
+ }
2309
+ function createBridgeDeleteDataTableRequest(assetPath, options = {}, requestId = randomUUID()) {
2310
+ return bridgeDeleteDataTableRequestSchema.parse({
2311
+ assetPath,
2312
+ dryRun: options.dryRun,
2313
+ force: options.force,
2314
+ requestId,
2315
+ type: BRIDGE_DELETE_DATA_TABLE_REQUEST_TYPE,
2316
+ version: 2
2317
+ });
2318
+ }
2319
+ function createBridgeRenameDataTableRowRequest(assetPath, rowName, newRowName, options = {}, requestId = randomUUID()) {
2320
+ return bridgeRenameDataTableRowRequestSchema.parse({
2321
+ assetPath,
2322
+ dryRun: options.dryRun,
2323
+ newRowName,
2324
+ requestId,
2325
+ rowName,
2326
+ type: BRIDGE_RENAME_DATA_TABLE_ROW_REQUEST_TYPE,
2327
+ version: 2
2328
+ });
2329
+ }
2005
2330
  function createBridgeFindAssetsRequest(options = {}, requestId = randomUUID()) {
2006
2331
  return bridgeFindAssetsRequestSchema.parse({
2007
2332
  classNames: options.classNames,
@@ -2569,6 +2894,30 @@ async function sendBridgeStartTraceRequest(request, options) {
2569
2894
  async function sendBridgeStopTraceRequest(request, options) {
2570
2895
  return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeStopTraceResponseSchema, request.requestId);
2571
2896
  }
2897
+ async function sendBridgeReadDataTableRequest(request, options) {
2898
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeReadDataTableResponseSchema, request.requestId);
2899
+ }
2900
+ async function sendBridgeWriteDataTableRowRequest(request, options) {
2901
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeWriteDataTableRowResponseSchema, request.requestId);
2902
+ }
2903
+ async function sendBridgeExportDataTableRequest(request, options) {
2904
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeExportDataTableResponseSchema, request.requestId);
2905
+ }
2906
+ async function sendBridgeImportDataTableRequest(request, options) {
2907
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeImportDataTableResponseSchema, request.requestId);
2908
+ }
2909
+ async function sendBridgeCreateDataTableRequest(request, options) {
2910
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeCreateDataTableResponseSchema, request.requestId);
2911
+ }
2912
+ async function sendBridgeRenameDataTableRequest(request, options) {
2913
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeRenameDataTableResponseSchema, request.requestId);
2914
+ }
2915
+ async function sendBridgeDeleteDataTableRequest(request, options) {
2916
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeDeleteDataTableResponseSchema, request.requestId);
2917
+ }
2918
+ async function sendBridgeRenameDataTableRowRequest(request, options) {
2919
+ return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeRenameDataTableRowResponseSchema, request.requestId);
2920
+ }
2572
2921
  async function sendBridgeFindAssetsRequest(request, options) {
2573
2922
  return parseBridgeResponse(await exchangeBridgeMessage(encodeBridgeFrame(request), options), bridgeFindAssetsResponseSchema, request.requestId);
2574
2923
  }
@@ -5747,6 +6096,62 @@ const findAssetsInputSchema = {
5747
6096
  recursiveClasses: bridgeFindAssetsRequestSchema.shape.recursiveClasses.describe("Whether classNames also match subclasses (default true)."),
5748
6097
  recursivePaths: bridgeFindAssetsRequestSchema.shape.recursivePaths.describe("Whether packagePaths also match sub-folders (default true).")
5749
6098
  };
6099
+ const readDataTableInputSchema = {
6100
+ assetPath: bridgeReadDataTableRequestSchema.shape.assetPath.describe("Object path of the DataTable asset, as find_assets reports it (e.g. /Game/Data/DT_Weapons.DT_Weapons)."),
6101
+ cursor: bridgeReadDataTableRequestSchema.shape.cursor.describe("Offset into the name-ordered row list (default 0). Pass the previous response nextCursor to fetch the next page."),
6102
+ 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."),
6103
+ 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."),
6104
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor holds the DataTable."),
6105
+ rowNameFilter: bridgeReadDataTableRequestSchema.shape.rowNameFilter.describe("Optional case-insensitive substring matched against each row name, applied before paging.")
6106
+ };
6107
+ const writeDataTableRowInputSchema = {
6108
+ assetPath: bridgeWriteDataTableRowRequestSchema.shape.assetPath.describe("Object path of the DataTable asset to write (e.g. /Game/Data/DT_Weapons.DT_Weapons)."),
6109
+ 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)."),
6110
+ 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."),
6111
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor holds the DataTable."),
6112
+ rowName: bridgeWriteDataTableRowRequestSchema.shape.rowName.describe("Name of the row to write or delete, as read_data_table reports it."),
6113
+ 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.")
6114
+ };
6115
+ const exportDataTableInputSchema = {
6116
+ assetPath: bridgeExportDataTableRequestSchema.shape.assetPath.describe("Object path of the DataTable asset to export (e.g. /Game/Data/DT_Weapons.DT_Weapons)."),
6117
+ cursor: bridgeExportDataTableRequestSchema.shape.cursor.describe("Character offset into the serialized table (default 0). Pass the previous response nextCursor to fetch the next chunk."),
6118
+ format: bridgeExportDataTableRequestSchema.shape.format.describe("Serialization to use: 'json' (default) or 'csv'. Both come from the engine's own DataTable exporters."),
6119
+ loadIfNeeded: bridgeExportDataTableRequestSchema.shape.loadIfNeeded.describe("Whether to load the asset when the editor does not already have it in memory (default true)."),
6120
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor holds the DataTable.")
6121
+ };
6122
+ const importDataTableInputSchema = {
6123
+ assetPath: bridgeImportDataTableRequestSchema.shape.assetPath.describe("Object path of the DataTable asset to overwrite (e.g. /Game/Data/DT_Weapons.DT_Weapons)."),
6124
+ 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.`),
6125
+ dryRun: bridgeImportDataTableRequestSchema.shape.dryRun.describe("When true, parse the content and report the row count and problems without replacing anything (default false)."),
6126
+ format: bridgeImportDataTableRequestSchema.shape.format.describe("Serialization the content is in: 'json' (default) or 'csv'."),
6127
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor holds the DataTable.")
6128
+ };
6129
+ const createDataTableInputSchema = {
6130
+ assetPath: bridgeCreateDataTableRequestSchema.shape.assetPath.describe("Object path for the new DataTable, e.g. /Game/Data/DT_Weapons. Fails if an asset already exists there."),
6131
+ dryRun: bridgeCreateDataTableRequestSchema.shape.dryRun.describe("When true, resolve and validate the row struct and destination without creating anything (default false)."),
6132
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor should create the table."),
6133
+ 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.")
6134
+ };
6135
+ const renameDataTableInputSchema = {
6136
+ assetPath: bridgeRenameDataTableRequestSchema.shape.assetPath.describe("Object path of the DataTable to rename or move (e.g. /Game/Data/DT_Weapons.DT_Weapons)."),
6137
+ destinationPath: bridgeRenameDataTableRequestSchema.shape.destinationPath.describe("Package path to move the asset into, e.g. /Game/Data/Archive. Omit to keep it where it is."),
6138
+ dryRun: bridgeRenameDataTableRequestSchema.shape.dryRun.describe("When true, validate the destination without moving anything (default false)."),
6139
+ newName: bridgeRenameDataTableRequestSchema.shape.newName.describe("New asset name without any path, e.g. DT_WeaponsLegacy. Omit to keep the current name."),
6140
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor holds the DataTable.")
6141
+ };
6142
+ const deleteDataTableInputSchema = {
6143
+ assetPath: bridgeDeleteDataTableRequestSchema.shape.assetPath.describe("Object path of the DataTable to delete (e.g. /Game/Data/DT_Weapons.DT_Weapons)."),
6144
+ dryRun: bridgeDeleteDataTableRequestSchema.shape.dryRun.describe("When true, report what would be deleted and what still references it, without deleting (default false)."),
6145
+ 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."),
6146
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor holds the DataTable.")
6147
+ };
6148
+ const renameDataTableRowInputSchema = {
6149
+ assetPath: bridgeRenameDataTableRowRequestSchema.shape.assetPath.describe("Object path of the DataTable holding the row (e.g. /Game/Data/DT_Weapons.DT_Weapons)."),
6150
+ dryRun: bridgeRenameDataTableRowRequestSchema.shape.dryRun.describe("When true, validate both names without renaming anything (default false)."),
6151
+ newRowName: bridgeRenameDataTableRowRequestSchema.shape.newRowName.describe("Name to rename the row to. Fails if a row already uses it."),
6152
+ projectId: projectIdSchema.describe("Canonical Unreal ProjectID whose registered editor holds the DataTable."),
6153
+ rowName: bridgeRenameDataTableRowRequestSchema.shape.rowName.describe("Current name of the row to rename.")
6154
+ };
5750
6155
  const listActorsInputSchema = {
5751
6156
  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
6157
  cursor: bridgeListActorsRequestSchema.shape.cursor.describe("Offset into the stable-ordered matched list (default 0). Pass the previous response nextCursor to fetch the next page."),
@@ -6464,6 +6869,110 @@ function registerMcpTools(server, dependencies) {
6464
6869
  recursivePaths
6465
6870
  }, dependencies));
6466
6871
  });
6872
+ server.registerTool("read_data_table", {
6873
+ 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.",
6874
+ inputSchema: readDataTableInputSchema,
6875
+ title: "Read Data Table"
6876
+ }, async ({ assetPath, cursor, loadIfNeeded, maxRows, projectId, rowNameFilter }) => {
6877
+ return toToolSuccess(await readDataTable({
6878
+ assetPath,
6879
+ cursor,
6880
+ loadIfNeeded,
6881
+ maxRows,
6882
+ projectId,
6883
+ rowNameFilter
6884
+ }, dependencies));
6885
+ });
6886
+ server.registerTool("write_data_table_row", {
6887
+ 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.",
6888
+ inputSchema: writeDataTableRowInputSchema,
6889
+ title: "Write Data Table Row"
6890
+ }, async ({ assetPath, dryRun, operation, projectId, rowName, values }) => {
6891
+ return toToolSuccess(await writeDataTableRow({
6892
+ assetPath,
6893
+ dryRun,
6894
+ operation,
6895
+ projectId,
6896
+ rowName,
6897
+ values
6898
+ }, dependencies));
6899
+ });
6900
+ server.registerTool("export_data_table", {
6901
+ 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.",
6902
+ inputSchema: exportDataTableInputSchema,
6903
+ title: "Export Data Table"
6904
+ }, async ({ assetPath, cursor, format, loadIfNeeded, projectId }) => {
6905
+ return toToolSuccess(await exportDataTable({
6906
+ assetPath,
6907
+ cursor,
6908
+ format,
6909
+ loadIfNeeded,
6910
+ projectId
6911
+ }, dependencies));
6912
+ });
6913
+ server.registerTool("import_data_table", {
6914
+ 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.",
6915
+ inputSchema: importDataTableInputSchema,
6916
+ title: "Import Data Table"
6917
+ }, async ({ assetPath, content, dryRun, format, projectId }) => {
6918
+ return toToolSuccess(await importDataTable({
6919
+ assetPath,
6920
+ content,
6921
+ dryRun,
6922
+ format,
6923
+ projectId
6924
+ }, dependencies));
6925
+ });
6926
+ server.registerTool("create_data_table", {
6927
+ 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.",
6928
+ inputSchema: createDataTableInputSchema,
6929
+ title: "Create Data Table"
6930
+ }, async ({ assetPath, dryRun, projectId, rowStructPath }) => {
6931
+ return toToolSuccess(await createDataTable({
6932
+ assetPath,
6933
+ dryRun,
6934
+ projectId,
6935
+ rowStructPath
6936
+ }, dependencies));
6937
+ });
6938
+ server.registerTool("rename_data_table", {
6939
+ 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.",
6940
+ inputSchema: renameDataTableInputSchema,
6941
+ title: "Rename Data Table"
6942
+ }, async ({ assetPath, destinationPath, dryRun, newName, projectId }) => {
6943
+ return toToolSuccess(await renameDataTable({
6944
+ assetPath,
6945
+ destinationPath,
6946
+ dryRun,
6947
+ newName,
6948
+ projectId
6949
+ }, dependencies));
6950
+ });
6951
+ server.registerTool("delete_data_table", {
6952
+ 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.",
6953
+ inputSchema: deleteDataTableInputSchema,
6954
+ title: "Delete Data Table"
6955
+ }, async ({ assetPath, dryRun, force, projectId }) => {
6956
+ return toToolSuccess(await deleteDataTable({
6957
+ assetPath,
6958
+ dryRun,
6959
+ force,
6960
+ projectId
6961
+ }, dependencies));
6962
+ });
6963
+ server.registerTool("rename_data_table_row", {
6964
+ 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.",
6965
+ inputSchema: renameDataTableRowInputSchema,
6966
+ title: "Rename Data Table Row"
6967
+ }, async ({ assetPath, dryRun, newRowName, projectId, rowName }) => {
6968
+ return toToolSuccess(await renameDataTableRow({
6969
+ assetPath,
6970
+ dryRun,
6971
+ newRowName,
6972
+ projectId,
6973
+ rowName
6974
+ }, dependencies));
6975
+ });
6467
6976
  server.registerTool("get_asset_dependencies", {
6468
6977
  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
6978
  inputSchema: getAssetDependenciesInputSchema,
@@ -6864,6 +7373,213 @@ async function findAssets(input, dependencies) {
6864
7373
  truncated: response.truncated
6865
7374
  };
6866
7375
  }
7376
+ async function readDataTable(input, dependencies) {
7377
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7378
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7379
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7380
+ const response = await sendBridgeReadDataTableRequest(createBridgeReadDataTableRequest(input.assetPath, {
7381
+ cursor: input.cursor,
7382
+ loadIfNeeded: input.loadIfNeeded,
7383
+ maxRows: input.maxRows,
7384
+ rowNameFilter: input.rowNameFilter
7385
+ }), {
7386
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7387
+ host: "127.0.0.1",
7388
+ port: entry.port,
7389
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7390
+ });
7391
+ if (!response.ok) throw new Error(`Bridge read_data_table failed (${response.error.code}): ${response.error.message}`);
7392
+ return {
7393
+ assetPath: response.assetPath,
7394
+ columns: response.columns,
7395
+ columnsTruncated: response.columnsTruncated,
7396
+ nextCursor: response.nextCursor,
7397
+ projectId: entry.projectId,
7398
+ rows: response.rows,
7399
+ rowStructName: response.rowStructName,
7400
+ rowStructPath: response.rowStructPath,
7401
+ totalRowCount: response.totalRowCount,
7402
+ truncated: response.truncated,
7403
+ valueTruncated: response.valueTruncated
7404
+ };
7405
+ }
7406
+ async function writeDataTableRow(input, dependencies) {
7407
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7408
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7409
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7410
+ const response = await sendBridgeWriteDataTableRowRequest(createBridgeWriteDataTableRowRequest(input.assetPath, input.rowName, input.operation, {
7411
+ dryRun: input.dryRun,
7412
+ values: input.values
7413
+ }), {
7414
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7415
+ host: "127.0.0.1",
7416
+ port: entry.port,
7417
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7418
+ });
7419
+ if (!response.ok) throw new Error(`Bridge write_data_table_row failed (${response.error.code}): ${response.error.message}`);
7420
+ return {
7421
+ applied: response.applied,
7422
+ assetPath: response.assetPath,
7423
+ created: response.created,
7424
+ dryRun: response.dryRun,
7425
+ message: response.message,
7426
+ packageDirty: response.packageDirty,
7427
+ projectId: entry.projectId,
7428
+ rowExisted: response.rowExisted,
7429
+ rowName: response.rowName,
7430
+ state: response.state,
7431
+ transactionTitle: response.transactionTitle
7432
+ };
7433
+ }
7434
+ async function exportDataTable(input, dependencies) {
7435
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7436
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7437
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7438
+ const response = await sendBridgeExportDataTableRequest(createBridgeExportDataTableRequest(input.assetPath, {
7439
+ cursor: input.cursor,
7440
+ format: input.format,
7441
+ loadIfNeeded: input.loadIfNeeded
7442
+ }), {
7443
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7444
+ host: "127.0.0.1",
7445
+ port: entry.port,
7446
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7447
+ });
7448
+ if (!response.ok) throw new Error(`Bridge export_data_table failed (${response.error.code}): ${response.error.message}`);
7449
+ return {
7450
+ assetPath: response.assetPath,
7451
+ content: response.content,
7452
+ format: response.format,
7453
+ nextCursor: response.nextCursor,
7454
+ projectId: entry.projectId,
7455
+ rowCount: response.rowCount,
7456
+ totalChars: response.totalChars,
7457
+ truncated: response.truncated
7458
+ };
7459
+ }
7460
+ async function importDataTable(input, dependencies) {
7461
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7462
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7463
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7464
+ const response = await sendBridgeImportDataTableRequest(createBridgeImportDataTableRequest(input.assetPath, input.content, {
7465
+ dryRun: input.dryRun,
7466
+ format: input.format
7467
+ }), {
7468
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7469
+ host: "127.0.0.1",
7470
+ port: entry.port,
7471
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7472
+ });
7473
+ if (!response.ok) throw new Error(`Bridge import_data_table failed (${response.error.code}): ${response.error.message}`);
7474
+ return {
7475
+ assetPath: response.assetPath,
7476
+ dryRun: response.dryRun,
7477
+ message: response.message,
7478
+ packageDirty: response.packageDirty,
7479
+ problemCount: response.problemCount,
7480
+ problems: response.problems,
7481
+ projectId: entry.projectId,
7482
+ replacedRowCount: response.replacedRowCount,
7483
+ rowCount: response.rowCount,
7484
+ state: response.state,
7485
+ transactionTitle: response.transactionTitle
7486
+ };
7487
+ }
7488
+ async function createDataTable(input, dependencies) {
7489
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7490
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7491
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7492
+ const response = await sendBridgeCreateDataTableRequest(createBridgeCreateDataTableRequest(input.assetPath, input.rowStructPath, { dryRun: input.dryRun }), {
7493
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7494
+ host: "127.0.0.1",
7495
+ port: entry.port,
7496
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7497
+ });
7498
+ if (!response.ok) throw new Error(`Bridge create_data_table failed (${response.error.code}): ${response.error.message}`);
7499
+ return {
7500
+ assetPath: response.assetPath,
7501
+ created: response.created,
7502
+ dryRun: response.dryRun,
7503
+ packageDirty: response.packageDirty,
7504
+ projectId: entry.projectId,
7505
+ rowStructPath: response.rowStructPath,
7506
+ state: response.state
7507
+ };
7508
+ }
7509
+ async function renameDataTable(input, dependencies) {
7510
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7511
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7512
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7513
+ const response = await sendBridgeRenameDataTableRequest(createBridgeRenameDataTableRequest(input.assetPath, {
7514
+ destinationPath: input.destinationPath,
7515
+ dryRun: input.dryRun,
7516
+ newName: input.newName
7517
+ }), {
7518
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7519
+ host: "127.0.0.1",
7520
+ port: entry.port,
7521
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7522
+ });
7523
+ if (!response.ok) throw new Error(`Bridge rename_data_table failed (${response.error.code}): ${response.error.message}`);
7524
+ return {
7525
+ assetPath: response.assetPath,
7526
+ dryRun: response.dryRun,
7527
+ leftRedirector: response.leftRedirector,
7528
+ newAssetPath: response.newAssetPath,
7529
+ packageDirty: response.packageDirty,
7530
+ projectId: entry.projectId,
7531
+ renamed: response.renamed,
7532
+ state: response.state
7533
+ };
7534
+ }
7535
+ async function deleteDataTable(input, dependencies) {
7536
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7537
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7538
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7539
+ const response = await sendBridgeDeleteDataTableRequest(createBridgeDeleteDataTableRequest(input.assetPath, {
7540
+ dryRun: input.dryRun,
7541
+ force: input.force
7542
+ }), {
7543
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7544
+ host: "127.0.0.1",
7545
+ port: entry.port,
7546
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7547
+ });
7548
+ if (!response.ok) throw new Error(`Bridge delete_data_table failed (${response.error.code}): ${response.error.message}`);
7549
+ return {
7550
+ assetPath: response.assetPath,
7551
+ deleted: response.deleted,
7552
+ dryRun: response.dryRun,
7553
+ forced: response.forced,
7554
+ projectId: entry.projectId,
7555
+ referencerCount: response.referencerCount,
7556
+ referencers: response.referencers,
7557
+ state: response.state
7558
+ };
7559
+ }
7560
+ async function renameDataTableRow(input, dependencies) {
7561
+ const normalizedProjectId = projectIdSchema.parse(input.projectId);
7562
+ const entry = dependencies.registry.getProject(normalizedProjectId);
7563
+ if (!entry) throw new Error(`No registered Unreal engine was found for projectId '${normalizedProjectId}'.`);
7564
+ const response = await sendBridgeRenameDataTableRowRequest(createBridgeRenameDataTableRowRequest(input.assetPath, input.rowName, input.newRowName, { dryRun: input.dryRun }), {
7565
+ connectTimeoutMs: deriveBridgeConnectTimeoutMs$1(dependencies.pingTimeoutMs),
7566
+ host: "127.0.0.1",
7567
+ port: entry.port,
7568
+ readTimeoutMs: deriveEditorWorkTimeoutMs(dependencies.pingTimeoutMs)
7569
+ });
7570
+ if (!response.ok) throw new Error(`Bridge rename_data_table_row failed (${response.error.code}): ${response.error.message}`);
7571
+ return {
7572
+ assetPath: response.assetPath,
7573
+ dryRun: response.dryRun,
7574
+ newRowName: response.newRowName,
7575
+ packageDirty: response.packageDirty,
7576
+ projectId: entry.projectId,
7577
+ renamed: response.renamed,
7578
+ rowName: response.rowName,
7579
+ state: response.state,
7580
+ transactionTitle: response.transactionTitle
7581
+ };
7582
+ }
6867
7583
  async function getAssetDependencies(input, dependencies) {
6868
7584
  const normalizedProjectId = projectIdSchema.parse(input.projectId);
6869
7585
  const entry = dependencies.registry.getProject(normalizedProjectId);
@@ -8229,7 +8945,7 @@ function isMissingFileError(error) {
8229
8945
  //#endregion
8230
8946
  //#region src/server.ts
8231
8947
  const SERVER_NAME = "ue-mcp-server";
8232
- const SERVER_VERSION = "1.0.0";
8948
+ const SERVER_VERSION = "1.1.0";
8233
8949
  const logger = createModuleLogger("bootstrap");
8234
8950
  const enginePingLogger = createModuleLogger("engine-ping");
8235
8951
  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.1.0",
4
4
  "description": "MCP server for making Unreal Engine and C++ development easier",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",