threenative-asset-mcp 0.9.5 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +260 -3
- package/dist/ambientcg/client.d.ts +7 -4
- package/dist/ambientcg/client.d.ts.map +1 -1
- package/dist/ambientcg/client.js +71 -62
- package/dist/ambientcg/client.js.map +1 -1
- package/dist/audio/generate.d.ts +3 -0
- package/dist/audio/generate.d.ts.map +1 -1
- package/dist/audio/generate.js +20 -2
- package/dist/audio/generate.js.map +1 -1
- package/dist/browser-temp.d.ts +13 -0
- package/dist/browser-temp.d.ts.map +1 -0
- package/dist/browser-temp.js +17 -0
- package/dist/browser-temp.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/creature/preview.d.ts.map +1 -1
- package/dist/creature/preview.js +41 -20
- package/dist/creature/preview.js.map +1 -1
- package/dist/creature/runner.d.ts +12 -1
- package/dist/creature/runner.d.ts.map +1 -1
- package/dist/creature/runner.js +30 -16
- package/dist/creature/runner.js.map +1 -1
- package/dist/data-texture-loader.d.ts +43 -0
- package/dist/data-texture-loader.d.ts.map +1 -0
- package/dist/data-texture-loader.js +67 -0
- package/dist/data-texture-loader.js.map +1 -0
- package/dist/discovery/acquisition.d.ts +45 -0
- package/dist/discovery/acquisition.d.ts.map +1 -0
- package/dist/discovery/acquisition.js +372 -0
- package/dist/discovery/acquisition.js.map +1 -0
- package/dist/discovery/adapters.d.ts +16 -0
- package/dist/discovery/adapters.d.ts.map +1 -0
- package/dist/discovery/adapters.js +557 -0
- package/dist/discovery/adapters.js.map +1 -0
- package/dist/discovery/federation.d.ts +187 -0
- package/dist/discovery/federation.d.ts.map +1 -0
- package/dist/discovery/federation.js +373 -0
- package/dist/discovery/federation.js.map +1 -0
- package/dist/discovery/metadata.d.ts +12 -0
- package/dist/discovery/metadata.d.ts.map +1 -0
- package/dist/discovery/metadata.js +88 -0
- package/dist/discovery/metadata.js.map +1 -0
- package/dist/discovery/types.d.ts +97 -0
- package/dist/discovery/types.d.ts.map +1 -0
- package/dist/discovery/types.js +31 -0
- package/dist/discovery/types.js.map +1 -0
- package/dist/download/direct-asset-downloader.d.ts +8 -0
- package/dist/download/direct-asset-downloader.d.ts.map +1 -1
- package/dist/download/direct-asset-downloader.js +92 -30
- package/dist/download/direct-asset-downloader.js.map +1 -1
- package/dist/fab/browser-transport.d.ts.map +1 -1
- package/dist/fab/browser-transport.js +33 -9
- package/dist/fab/browser-transport.js.map +1 -1
- package/dist/fab/fabcli.d.ts +30 -3
- package/dist/fab/fabcli.d.ts.map +1 -1
- package/dist/fab/fabcli.js +116 -13
- package/dist/fab/fabcli.js.map +1 -1
- package/dist/fab/routes.d.ts +23 -0
- package/dist/fab/routes.d.ts.map +1 -0
- package/dist/fab/routes.js +63 -0
- package/dist/fab/routes.js.map +1 -0
- package/dist/kenney/client.d.ts +41 -0
- package/dist/kenney/client.d.ts.map +1 -0
- package/dist/kenney/client.js +200 -0
- package/dist/kenney/client.js.map +1 -0
- package/dist/polyhaven/client.d.ts +9 -5
- package/dist/polyhaven/client.d.ts.map +1 -1
- package/dist/polyhaven/client.js +99 -55
- package/dist/polyhaven/client.js.map +1 -1
- package/dist/rig/animation-catalog.json +1 -1
- package/dist/rig/preview.d.ts +12 -1
- package/dist/rig/preview.d.ts.map +1 -1
- package/dist/rig/preview.js +130 -65
- package/dist/rig/preview.js.map +1 -1
- package/dist/rig/retarget.d.ts +14 -0
- package/dist/rig/retarget.d.ts.map +1 -1
- package/dist/rig/retarget.js +224 -36
- package/dist/rig/retarget.js.map +1 -1
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +52 -1
- package/dist/server.js.map +1 -1
- package/dist/sketchfab/client.d.ts +8 -5
- package/dist/sketchfab/client.d.ts.map +1 -1
- package/dist/sketchfab/client.js +71 -66
- package/dist/sketchfab/client.js.map +1 -1
- package/dist/smithsonian/client.d.ts +6 -3
- package/dist/smithsonian/client.d.ts.map +1 -1
- package/dist/smithsonian/client.js +65 -48
- package/dist/smithsonian/client.js.map +1 -1
- package/dist/tools/creature.d.ts +1 -1
- package/dist/tools/creature.d.ts.map +1 -1
- package/dist/tools/federated-assets.d.ts +381 -0
- package/dist/tools/federated-assets.d.ts.map +1 -0
- package/dist/tools/federated-assets.js +143 -0
- package/dist/tools/federated-assets.js.map +1 -0
- package/dist/tools/import-unreal.d.ts +82 -0
- package/dist/tools/import-unreal.d.ts.map +1 -1
- package/dist/tools/import-unreal.js +106 -6
- package/dist/tools/import-unreal.js.map +1 -1
- package/dist/tools/rig.d.ts +4 -0
- package/dist/tools/rig.d.ts.map +1 -1
- package/dist/tools/rig.js +10 -2
- package/dist/tools/rig.js.map +1 -1
- package/dist/tools/source-directory.js +1 -1
- package/dist/tools/source-directory.js.map +1 -1
- package/dist/unreal/albedo-fidelity.d.ts +86 -0
- package/dist/unreal/albedo-fidelity.d.ts.map +1 -0
- package/dist/unreal/albedo-fidelity.js +207 -0
- package/dist/unreal/albedo-fidelity.js.map +1 -0
- package/dist/unreal/animation-data.d.ts +342 -0
- package/dist/unreal/animation-data.d.ts.map +1 -0
- package/dist/unreal/animation-data.js +375 -0
- package/dist/unreal/animation-data.js.map +1 -0
- package/dist/unreal/contact-sheet.d.ts +231 -0
- package/dist/unreal/contact-sheet.d.ts.map +1 -0
- package/dist/unreal/contact-sheet.js +809 -0
- package/dist/unreal/contact-sheet.js.map +1 -0
- package/dist/unreal/cooking.d.ts +82 -0
- package/dist/unreal/cooking.d.ts.map +1 -1
- package/dist/unreal/cooking.js +287 -6
- package/dist/unreal/cooking.js.map +1 -1
- package/dist/unreal/cue4parse-adapter.d.ts +7 -1
- package/dist/unreal/cue4parse-adapter.d.ts.map +1 -1
- package/dist/unreal/cue4parse-adapter.js +4639 -239
- package/dist/unreal/cue4parse-adapter.js.map +1 -1
- package/dist/unreal/data-texture-promotion.d.ts +49 -0
- package/dist/unreal/data-texture-promotion.d.ts.map +1 -0
- package/dist/unreal/data-texture-promotion.js +104 -0
- package/dist/unreal/data-texture-promotion.js.map +1 -0
- package/dist/unreal/data-texture.d.ts +79 -0
- package/dist/unreal/data-texture.d.ts.map +1 -0
- package/dist/unreal/data-texture.js +186 -0
- package/dist/unreal/data-texture.js.map +1 -0
- package/dist/unreal/effects-dump.d.ts +81 -0
- package/dist/unreal/effects-dump.d.ts.map +1 -0
- package/dist/unreal/effects-dump.js +113 -0
- package/dist/unreal/effects-dump.js.map +1 -0
- package/dist/unreal/effects-preview.d.ts +76 -0
- package/dist/unreal/effects-preview.d.ts.map +1 -0
- package/dist/unreal/effects-preview.js +386 -0
- package/dist/unreal/effects-preview.js.map +1 -0
- package/dist/unreal/effects-schema.d.ts +1797 -0
- package/dist/unreal/effects-schema.d.ts.map +1 -0
- package/dist/unreal/effects-schema.js +1670 -0
- package/dist/unreal/effects-schema.js.map +1 -0
- package/dist/unreal/engine-content.d.ts +40 -0
- package/dist/unreal/engine-content.d.ts.map +1 -0
- package/dist/unreal/engine-content.js +96 -0
- package/dist/unreal/engine-content.js.map +1 -0
- package/dist/unreal/errors.d.ts +13 -0
- package/dist/unreal/errors.d.ts.map +1 -0
- package/dist/unreal/errors.js +11 -0
- package/dist/unreal/errors.js.map +1 -0
- package/dist/unreal/executable-publish.d.ts +15 -0
- package/dist/unreal/executable-publish.d.ts.map +1 -0
- package/dist/unreal/executable-publish.js +46 -0
- package/dist/unreal/executable-publish.js.map +1 -0
- package/dist/unreal/fidelity-baseline.d.ts +30 -0
- package/dist/unreal/fidelity-baseline.d.ts.map +1 -0
- package/dist/unreal/fidelity-baseline.js +52 -0
- package/dist/unreal/fidelity-baseline.js.map +1 -0
- package/dist/unreal/game-path.d.ts +7 -0
- package/dist/unreal/game-path.d.ts.map +1 -0
- package/dist/unreal/game-path.js +12 -0
- package/dist/unreal/game-path.js.map +1 -0
- package/dist/unreal/gameplay-data.d.ts +350 -0
- package/dist/unreal/gameplay-data.d.ts.map +1 -0
- package/dist/unreal/gameplay-data.js +534 -0
- package/dist/unreal/gameplay-data.js.map +1 -0
- package/dist/unreal/graph-baker.d.ts +222 -0
- package/dist/unreal/graph-baker.d.ts.map +1 -0
- package/dist/unreal/graph-baker.js +628 -0
- package/dist/unreal/graph-baker.js.map +1 -0
- package/dist/unreal/graph-dump.d.ts +213 -0
- package/dist/unreal/graph-dump.d.ts.map +1 -0
- package/dist/unreal/graph-dump.js +345 -0
- package/dist/unreal/graph-dump.js.map +1 -0
- package/dist/unreal/grooms.d.ts +35 -0
- package/dist/unreal/grooms.d.ts.map +1 -0
- package/dist/unreal/grooms.js +297 -0
- package/dist/unreal/grooms.js.map +1 -0
- package/dist/unreal/image-diff.d.ts +129 -0
- package/dist/unreal/image-diff.d.ts.map +1 -0
- package/dist/unreal/image-diff.js +489 -0
- package/dist/unreal/image-diff.js.map +1 -0
- package/dist/unreal/importer.d.ts +371 -11
- package/dist/unreal/importer.d.ts.map +1 -1
- package/dist/unreal/importer.js +3214 -203
- package/dist/unreal/importer.js.map +1 -1
- package/dist/unreal/material-graph.d.ts +260 -0
- package/dist/unreal/material-graph.d.ts.map +1 -0
- package/dist/unreal/material-graph.js +3479 -0
- package/dist/unreal/material-graph.js.map +1 -0
- package/dist/unreal/material-metadata.d.ts +39 -0
- package/dist/unreal/material-metadata.d.ts.map +1 -0
- package/dist/unreal/material-metadata.js +120 -0
- package/dist/unreal/material-metadata.js.map +1 -0
- package/dist/unreal/materials.d.ts +55 -5
- package/dist/unreal/materials.d.ts.map +1 -1
- package/dist/unreal/materials.js +360 -61
- package/dist/unreal/materials.js.map +1 -1
- package/dist/unreal/mesh-build-scale.d.ts +9 -0
- package/dist/unreal/mesh-build-scale.d.ts.map +1 -0
- package/dist/unreal/mesh-build-scale.js +123 -0
- package/dist/unreal/mesh-build-scale.js.map +1 -0
- package/dist/unreal/package-thumbnail.d.ts +42 -0
- package/dist/unreal/package-thumbnail.d.ts.map +1 -0
- package/dist/unreal/package-thumbnail.js +219 -0
- package/dist/unreal/package-thumbnail.js.map +1 -0
- package/dist/unreal/parity-run.d.ts +438 -0
- package/dist/unreal/parity-run.d.ts.map +1 -0
- package/dist/unreal/parity-run.js +1001 -0
- package/dist/unreal/parity-run.js.map +1 -0
- package/dist/unreal/parity.d.ts +138 -0
- package/dist/unreal/parity.d.ts.map +1 -0
- package/dist/unreal/parity.js +538 -0
- package/dist/unreal/parity.js.map +1 -0
- package/dist/unreal/preview-reference-workflow.d.ts +77 -0
- package/dist/unreal/preview-reference-workflow.d.ts.map +1 -0
- package/dist/unreal/preview-reference-workflow.js +106 -0
- package/dist/unreal/preview-reference-workflow.js.map +1 -0
- package/dist/unreal/property-dump.d.ts +128 -0
- package/dist/unreal/property-dump.d.ts.map +1 -0
- package/dist/unreal/property-dump.js +83 -0
- package/dist/unreal/property-dump.js.map +1 -0
- package/dist/unreal/provision.d.ts +145 -13
- package/dist/unreal/provision.d.ts.map +1 -1
- package/dist/unreal/provision.js +711 -144
- package/dist/unreal/provision.js.map +1 -1
- package/dist/unreal/psa.d.ts +45 -1
- package/dist/unreal/psa.d.ts.map +1 -1
- package/dist/unreal/psa.js +95 -12
- package/dist/unreal/psa.js.map +1 -1
- package/dist/unreal/render-fidelity.d.ts +142 -0
- package/dist/unreal/render-fidelity.d.ts.map +1 -0
- package/dist/unreal/render-fidelity.js +361 -0
- package/dist/unreal/render-fidelity.js.map +1 -0
- package/dist/unreal/scenes.d.ts +12 -0
- package/dist/unreal/scenes.d.ts.map +1 -1
- package/dist/unreal/scenes.js +64 -2
- package/dist/unreal/scenes.js.map +1 -1
- package/dist/unreal/source-material-package.d.ts +36 -0
- package/dist/unreal/source-material-package.d.ts.map +1 -0
- package/dist/unreal/source-material-package.js +320 -0
- package/dist/unreal/source-material-package.js.map +1 -0
- package/dist/unreal/source-material.d.ts +42 -0
- package/dist/unreal/source-material.d.ts.map +1 -0
- package/dist/unreal/source-material.js +400 -0
- package/dist/unreal/source-material.js.map +1 -0
- package/dist/unreal/spatial-atlas.d.ts +39 -0
- package/dist/unreal/spatial-atlas.d.ts.map +1 -0
- package/dist/unreal/spatial-atlas.js +302 -0
- package/dist/unreal/spatial-atlas.js.map +1 -0
- package/dist/unreal/spatial-remap.d.ts +54 -0
- package/dist/unreal/spatial-remap.d.ts.map +1 -0
- package/dist/unreal/spatial-remap.js +273 -0
- package/dist/unreal/spatial-remap.js.map +1 -0
- package/dist/unreal/spatial-surface.d.ts +88 -0
- package/dist/unreal/spatial-surface.d.ts.map +1 -0
- package/dist/unreal/spatial-surface.js +362 -0
- package/dist/unreal/spatial-surface.js.map +1 -0
- package/dist/unreal/static-mesh-sections.d.ts +58 -0
- package/dist/unreal/static-mesh-sections.d.ts.map +1 -0
- package/dist/unreal/static-mesh-sections.js +475 -0
- package/dist/unreal/static-mesh-sections.js.map +1 -0
- package/dist/unreal/surface-normals.d.ts +21 -0
- package/dist/unreal/surface-normals.d.ts.map +1 -0
- package/dist/unreal/surface-normals.js +106 -0
- package/dist/unreal/surface-normals.js.map +1 -0
- package/dist/unreal/texture-proof.d.ts +135 -0
- package/dist/unreal/texture-proof.d.ts.map +1 -0
- package/dist/unreal/texture-proof.js +439 -0
- package/dist/unreal/texture-proof.js.map +1 -0
- package/dist/unreal/thumbnail-floor.d.ts +45 -0
- package/dist/unreal/thumbnail-floor.d.ts.map +1 -0
- package/dist/unreal/thumbnail-floor.js +83 -0
- package/dist/unreal/thumbnail-floor.js.map +1 -0
- package/dist/unreal/thumbnail-lighting.d.ts +62 -0
- package/dist/unreal/thumbnail-lighting.d.ts.map +1 -0
- package/dist/unreal/thumbnail-lighting.js +125 -0
- package/dist/unreal/thumbnail-lighting.js.map +1 -0
- package/dist/unreal/thumbnail-reference-profile.d.ts +39 -0
- package/dist/unreal/thumbnail-reference-profile.d.ts.map +1 -0
- package/dist/unreal/thumbnail-reference-profile.js +218 -0
- package/dist/unreal/thumbnail-reference-profile.js.map +1 -0
- package/dist/unreal/toolchain.d.ts.map +1 -1
- package/dist/unreal/toolchain.js +4 -0
- package/dist/unreal/toolchain.js.map +1 -1
- package/dist/unreal/umodel-export.d.ts +15 -0
- package/dist/unreal/umodel-export.d.ts.map +1 -0
- package/dist/unreal/umodel-export.js +90 -0
- package/dist/unreal/umodel-export.js.map +1 -0
- package/dist/unreal/visual-judge.d.ts +100 -0
- package/dist/unreal/visual-judge.d.ts.map +1 -0
- package/dist/unreal/visual-judge.js +200 -0
- package/dist/unreal/visual-judge.js.map +1 -0
- package/package.json +18 -5
package/README.md
CHANGED
|
@@ -34,7 +34,12 @@ dependency relationships with pagination and resolution/format filters.
|
|
|
34
34
|
- Node.js 20.19 or newer
|
|
35
35
|
- A local environment capable of running Playwright Chromium when Fab requests
|
|
36
36
|
browser verification
|
|
37
|
-
- No Epic or Fab login is required or automated
|
|
37
|
+
- No Epic or Fab login is required or automated for search and free downloads.
|
|
38
|
+
`fab_import_asset` and `fab_list_owned` use your own FabCLI session; see
|
|
39
|
+
[Fab login for owned packs](#fab-login-for-owned-packs)
|
|
40
|
+
- **On Linux, `g++`, `perl`, `zlib1g-dev`, `libpng-dev` and `libsdl2-dev`** for Unreal imports
|
|
41
|
+
of UE4.0–4.20 packs. UE Viewer's Linux prebuilt is a 32-bit binary that 64-bit-only
|
|
42
|
+
hosts cannot run, so the importer builds it from source once
|
|
38
43
|
- **FFmpeg and FFprobe on `PATH`** for `audio_inspect_asset` and
|
|
39
44
|
`audio_generate_sound`. Without them nothing is decoded, and the tools report
|
|
40
45
|
that they could not check rather than reporting a pass.
|
|
@@ -140,6 +145,14 @@ Fab:
|
|
|
140
145
|
flows.
|
|
141
146
|
- `asset_import_unreal` — converts an already-downloaded Unreal directory to
|
|
142
147
|
self-contained GLBs plus `import-report.json`; Unreal Engine is not required.
|
|
148
|
+
Cascade `ParticleSystem` and Niagara systems are exported as data-only
|
|
149
|
+
descriptors under `Effects/<system>-<hash>.<cascade|niagara>.json`, one per
|
|
150
|
+
canonical package and object path so two same-basename systems in different
|
|
151
|
+
folders stay distinct. No runtime plays them: each descriptor lists the
|
|
152
|
+
modules and inputs it does not carry, and a placed effect component points at
|
|
153
|
+
its descriptor through `import-report.json`. Effects with no exported
|
|
154
|
+
descriptor stay named in the report; the importer never reconstructs a
|
|
155
|
+
particle simulation.
|
|
143
156
|
- `fab_import_asset` — downloads an owned listing through the user's existing
|
|
144
157
|
FabCLI session, then runs the same Unreal importer. It never signs in, claims,
|
|
145
158
|
or purchases an asset.
|
|
@@ -316,6 +329,38 @@ Unified source and download routing:
|
|
|
316
329
|
revalidated, existing files are never overwritten, and the result includes
|
|
317
330
|
SHA-256.
|
|
318
331
|
|
|
332
|
+
The additive `asset_search` tool searches individual `3d-model`, `texture`,
|
|
333
|
+
`hdri` and `animation` candidates across supported clients. It reports each
|
|
334
|
+
provider, bounded results and local continuation actions. Unknown prices fail
|
|
335
|
+
`freeOnly`; unknown rights fail explicit commercial-use/attribution filters.
|
|
336
|
+
Fab remains a specialized action and generic Sketchfab uses anonymous metadata,
|
|
337
|
+
without authenticated acquisition. Existing tools retain their public names.
|
|
338
|
+
|
|
339
|
+
Use `asset_get` with the returned stable `assetId` to inspect current terms and
|
|
340
|
+
available variants. It prefers GLB, then glTF with its supplied dependencies;
|
|
341
|
+
explicit unavailable formats return alternatives. The read-only call returns an
|
|
342
|
+
expiring `planToken`, required relative files and acknowledgement information.
|
|
343
|
+
A plan is not consent. Call `asset_download` with that token and explicit
|
|
344
|
+
`acceptLicense: true` only after reviewing the terms. Optional `maxBytes` can
|
|
345
|
+
only tighten the configured cap. Changed terms/files invalidate the plan.
|
|
346
|
+
|
|
347
|
+
Acquisition stages originals under `ASSET_DOWNLOAD_DIR/acquisitions`, verifies
|
|
348
|
+
checksums and all glTF buffer/texture references, then atomically publishes a
|
|
349
|
+
complete directory with `asset.acquisition.json`. Replays verify cached bytes
|
|
350
|
+
and provenance; a corrupt cache fails rather than being silently accepted.
|
|
351
|
+
Receipts preserve asset attribution separately from Poly Haven API credit.
|
|
352
|
+
No transfer/signed URLs or credentials enter the new public plan or receipt.
|
|
353
|
+
Archives stay archives with `requiresExtraction: true`; runtime readiness is
|
|
354
|
+
`unverified`. Acquiring originals does not change or adopt assets into a project.
|
|
355
|
+
|
|
356
|
+
Kenney 3D packs are enabled by default; `ASSET_ENABLE_KENNEY=0` disables the
|
|
357
|
+
generic adapter. Each query uses one official listing page and at most three
|
|
358
|
+
item detail checks,
|
|
359
|
+
paces requests, verifies item CC0/free/official-ZIP evidence and never crawls
|
|
360
|
+
or extracts archives. One official Mini Dungeon archive was qualified through
|
|
361
|
+
the packed public MCP on 2026-10-06. Broader catalog coverage and runtime import
|
|
362
|
+
remain unverified; ordinary tests use hand-authored local fixtures.
|
|
363
|
+
|
|
319
364
|
Recommended agent flow:
|
|
320
365
|
|
|
321
366
|
1. Call `asset_search_sources` with the requested category/query. Its default
|
|
@@ -346,18 +391,64 @@ it out of process. Modern UE5 packages rejected by UE Viewer use a pinned,
|
|
|
346
391
|
out-of-process CUE4Parse adapter. Set `THREENATIVE_TOOLCHAIN_AUTOINSTALL=0` to
|
|
347
392
|
require manually installed tools instead.
|
|
348
393
|
|
|
394
|
+
### Fab login for owned packs
|
|
395
|
+
|
|
396
|
+
`fab_import_asset` and `fab_list_owned` read your Fab library through
|
|
397
|
+
[FabCLI](https://github.com/zirklerite/FabCLI), which the importer installs on first
|
|
398
|
+
use. The MCP never signs in for you. Log in once; the session is kept in the OS
|
|
399
|
+
keyring (Secret Service on Linux) and lasts about 90 days:
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
~/.cache/threenative-asset-mcp/toolchain/fabcli/fabcli auth login # opens a login window
|
|
403
|
+
~/.cache/threenative-asset-mcp/toolchain/fabcli/fabcli auth login --manual # no window: paste a code
|
|
404
|
+
~/.cache/threenative-asset-mcp/toolchain/fabcli/fabcli auth status
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
`--manual` reuses the browser where you are already signed in to Epic. FabCLI prints a
|
|
408
|
+
`epicgames.com/id/login?redirectUrl=…` link; open it there, copy `authorizationCode` from the
|
|
409
|
+
JSON the page shows, and paste it into the prompt within a few minutes. It needs an
|
|
410
|
+
interactive terminal. In Claude Code, run it with a `!` prefix. Never paste the code into an
|
|
411
|
+
untrusted service: it grants full access to the Epic account.
|
|
412
|
+
|
|
413
|
+
`fab_list_owned` lists each listing's Unreal artifacts separately (`artifacts[]`: `artifactId`,
|
|
414
|
+
`engineVersions`, `oldestEngine`, decoder `route`, `targetPlatforms`). An artifact's format is its
|
|
415
|
+
oldest listed engine, and that picks the decoder. When a listing has several artifacts and
|
|
416
|
+
`fab_import_asset` gets neither `engine` nor `artifactId`, it takes the best route (CUE4Parse, then
|
|
417
|
+
the MeshDescription converter, then UE Viewer; the newest source format breaks a tie) and says so
|
|
418
|
+
in the report `warnings`. Pass `artifactId` or `engine` to override.
|
|
419
|
+
|
|
420
|
+
**Graph-only colour.** Some Unreal materials compute BaseColor in the material graph (layered
|
|
421
|
+
masters that blend colour textures by mask channels and tint them), so no texture binding can
|
|
422
|
+
reproduce them. A section left without a base colour is baked from its graph: the pack's editor
|
|
423
|
+
package is read with CUE4Parse, a closed node set is evaluated per texel, and the result is a
|
|
424
|
+
base-colour PNG with `source: "graph"` in the section's bindings. The bake is `exact` only when
|
|
425
|
+
every node is understood; engine layer functions that are not in the pack, a mesh without vertex
|
|
426
|
+
colours standing in white, and a UV set the importer drops make it `heuristic`, and the section's
|
|
427
|
+
`graph` field names each approximation. View-dependent nodes (`Fresnel`, `DepthFade`, `TwoSidedSign`)
|
|
428
|
+
stand in as a recorded approximation: a bake has no camera, so `Fresnel` is its mean over a sphere's visible
|
|
429
|
+
surface, `DepthFade` is fully faded in and `TwoSidedSign` is the front face. Nodes it cannot evaluate (for
|
|
430
|
+
example `ReflectionVectorWS` feeding a cubemap lookup, or `Time`) leave the neutral fallback and are listed
|
|
431
|
+
under `graph.unsupportedNodes`.
|
|
432
|
+
The first bake installs the CUE4Parse converter and its private .NET SDK (hundreds of MB, once);
|
|
433
|
+
pass `graphBake: false` to the importer, or set `THREENATIVE_TOOLCHAIN_AUTOINSTALL=0`, to skip it.
|
|
434
|
+
`fab_import_asset` also skips it when its environment has `THREENATIVE_GRAPH_BAKE=0` (the parity
|
|
435
|
+
sweep's `--no-graph-bake` uses this to produce a baseline).
|
|
436
|
+
|
|
349
437
|
| Input | Current result |
|
|
350
438
|
| --- | --- |
|
|
351
439
|
| Cooked loose UE4 static meshes and textures | GLB geometry, LOD sections, embedded textures, and common PBR reconstruction |
|
|
352
440
|
| Cooked loose UE4 skeletal meshes and standalone animation packages | Standard skinned GLB; joints, weights, inverse-bind matrices, and existing clips are preserved; ActorX PSA clips are attached by case-insensitive bone name when at least 80% of tracks match, without duplicating an existing clip name |
|
|
441
|
+
| UE5 `AnimSequence`, blend spaces, aim offsets, montages and composites | UE5.0–5.5 cooked clips become glTF animations through CUE4Parse and attach by bone name; blend spaces, aim offsets, montages and composites become `Animation/<name>.json` with typed axes, mirror flags and resolved section starts, and each clip gets `<clip>.clip.json` notifies, curves and sync markers. A clip with no raw or compressed bone data is reported from its package's own evidence (a `DataModel` export, a `ControlRig` model, or `NumFrames`) instead of exporting a bind pose |
|
|
353
442
|
| Uncooked UE4 `FMeshDescription` static meshes (object versions 517–522) | GLB geometry with centimetres converted to metres; verified on Fab Office Pack Vol.1 (47/47 meshes) |
|
|
354
443
|
| Uncooked UE5 editor static meshes (source models only, no render data) | LOD0 of the `FMeshDescription` source model is decoded from the package trailer's Oodle payloads, with the cooked glTF writer's axes, scale, and material naming. Verified on Fab's Megascans Common Hazel and European Hornbeam UE5.1 artifacts: triangle counts match the UE4.27 decoder of the same scans exactly |
|
|
355
444
|
| Uncooked UE4.0–4.20 static and skeletal meshes (object versions below 517) | Exported by UE Viewer from the editor source data, with materials, skins, and ActorX clips like cooked packages. Verified on Fab packs from UE4.10 to UE4.20: Modular Building Set (129 meshes), Animal Variety Pack (6 rigged animals, 146 clips), STF Landscape Pro 2.0 (61), Power Plant (65), and a UE4.19 fern pack (38) |
|
|
356
445
|
| Standalone `Texture2D` packages | Collision-free source-relative PNGs under `textures/`; duplicate Unreal basenames are exported independently instead of overwriting one another |
|
|
446
|
+
| `Texture2D` with an `RGBA16F` source | Raw little-endian half-float RGBA binaries and a versioned JSON manifest under `data-textures/`, promoted together under the package's qualified path. Values are stored, not decoded: no PivotPainter, classifier, source UV, graph or WPO semantics, and no material binding. The PNG exporters cannot encode the format, so the importer asks the converter for this route after a legacy PNG export fails; a UE 4.x pack needs its `engine` (`UE_4.19`), since the default detector reads every UE4 package as the latest UE4. Loaded in the browser with `threenative-asset-mcp/data-texture` (see Three.js usage) |
|
|
357
447
|
| `TextureCube` environment maps | Collision-free 2:1 equirectangular PNG or lossless Radiance HDR files under `cubemaps/`; CUE4Parse decodes cooked faces, while editor `TSF_BGRE8` source art retains its lighting range without Unreal Engine |
|
|
358
448
|
| `Texture2DArray`, `TextureCubeArray`, and `VolumeTexture` | Collision-free RGBA8 slice data plus JSON dimensions under `textures3d/`; use `DataArrayTexture` for arrays and `Data3DTexture` for volumes. Cube-array faces remain ordered in groups of six for custom shaders. Floating-point/HDR stacks are rejected rather than reduced to RGBA8 |
|
|
359
449
|
| Standalone `Material` and `MaterialInstanceConstant` packages | One directly loadable `Materials/UnrealMaterialLibrary.glb`; each source-relative swatch name is collision-free, follows parent instances, embeds resolved texture inputs, and applies common base-color/emissive vectors plus roughness/metallic/opacity scalars |
|
|
360
450
|
| Standalone `SoundWave` packages | Collision-free WAV/Ogg/MP3/FLAC files under `audio/`; modern UE5 editor source WAVs and cooked formats exposed by CUE4Parse feed the same report path, verified through Three.js `AudioLoader` |
|
|
451
|
+
| Skeletal and static mesh sockets, static-mesh simple collision, `PhysicsAsset`, and `SoundCue` graphs | Socket nodes (`socket:<name>`) under the joint their bone names (or the mesh node for a static socket); simple collision on the mesh node's `extras.unrealCollision` and in `Collision/<package>.json`; physics bodies, constraints (limits, motions and drives) and collision-disabled pairs in metre-scale `Physics/<asset>.json`, referenced from the skinned mesh node; and each SoundCue graph in `Audio/<cue>.cue.json` with every `WavePlayer` leaf rewritten to the exported audio file or reported as a missing wave. A UE4 package that holds one of these classes routes to the CUE4Parse adapter, which UE Viewer cannot decode; an ordinary UE4 mesh whose only gameplay signal is a `BodySetup` or a cross-package PhysicsAsset/Skeleton reference joins the adapter run for its metadata while keeping its verified UE Viewer geometry route |
|
|
361
452
|
| Runtime `Font` and inline `FontFace` packages | Validated TTF/OTF faces under collision-free `fonts/` paths; direct SFNT and legacy multi-block zlib `FontBulkData` are supported without Unreal Engine, with family/style/weight metadata for the browser `FontFace` API |
|
|
362
453
|
| Offline `Font` packages | Pre-baked glyph pages and serialized `FontCharacter` rectangles become collision-safe atlas PNGs plus BMFont-compatible JSON under `bitmap-fonts/`; Unicode remaps, baseline metrics, kerning, page indices, and distance-field metadata are retained for Three.js bitmap/SDF text |
|
|
363
454
|
| Paper2D `PaperSprite` and `PaperFlipbook` packages | Each sprite becomes a self-contained, unlit GLB with exact baked triangles and a cropped atlas region; flipbooks become JSON manifests preserving FPS and per-frame run lengths. Verified in Three.js on a real UE5.2 Paper2D project without Unreal Engine |
|
|
@@ -371,9 +462,110 @@ require manually installed tools instead.
|
|
|
371
462
|
| UE4 ISM/HISM and painted static-mesh foliage placement | Bulk-serialized instance matrices become `EXT_mesh_gpu_instancing`, which Three.js `GLTFLoader` loads as GPU-instanced meshes |
|
|
372
463
|
| UE4 editor `LandscapeComponent` heightfields | Package-relative compressed BGRA8 heightmaps become indexed terrain meshes with decoded normals, component transforms, and original material names |
|
|
373
464
|
| ActorX per-frame bone scale, dynamic Blueprint bytecode/construction scripts, Paper Terrain/spline deformation, and Nanite-only data without a fallback mesh | Detected or reported honestly; scene-level omissions are listed in `scenes[].omittedActors`, and Blueprint bytecode is never executed by the importer |
|
|
374
|
-
| Arbitrary Unreal shader graphs |
|
|
465
|
+
| Arbitrary Unreal shader graphs | Recoverable PBR inputs become standard glTF materials. Reports mark materials as degraded when shader connections are unavailable, even if every section has a texture; section `limitations` explain omitted layering, graph UV transforms, subsurface lighting, normal strength, and deformation |
|
|
375
466
|
| Encrypted Pak/IoStore | Unsupported without user-supplied keys and archive extraction; never reported as a complete conversion |
|
|
376
467
|
|
|
468
|
+
Importer version 49 also respects material-instance override flags and modern
|
|
469
|
+
parameter names. Contradictory texture samples duplicated across distinct mesh
|
|
470
|
+
section families are repaired only when referenced texture families are
|
|
471
|
+
unambiguous in the same export namespace. These bindings remain heuristic.
|
|
472
|
+
Separate same-resolution opacity maps can supply base-colour alpha without
|
|
473
|
+
altering RGB; different source dimensions are currently unsupported. Existing
|
|
474
|
+
cached conversions are invalidated. A textured result does not certify the
|
|
475
|
+
appearance of the original Unreal shader.
|
|
476
|
+
|
|
477
|
+
Importer version 51 uses adaptive PNG row filtering for composed opacity maps,
|
|
478
|
+
preserving the previous encoder's decoded RGBA and PNG metadata. Untouched textures
|
|
479
|
+
retain their original bytes; their profiles, bit depth, and metadata are not
|
|
480
|
+
re-encoded for storage savings.
|
|
481
|
+
|
|
482
|
+
Importer version 56 stops a colour parameter's zero alpha from becoming the base
|
|
483
|
+
colour factor's opacity (a `Tint` of `A=0` clipped every pixel of a masked grass
|
|
484
|
+
card, so the GLB drew nothing), binds a masked or translucent material's separate
|
|
485
|
+
opacity map (`Opacity=` or an `Other` named `*_Opacity*`) as the base colour's alpha
|
|
486
|
+
heuristically, and composes an opacity map of another resolution when the aspect
|
|
487
|
+
ratio matches. A sweep sheet tile that renders nothing although Unreal has a
|
|
488
|
+
thumbnail for it is recorded under the entry's `sheet.warnings`.
|
|
489
|
+
|
|
490
|
+
Importer version 58 stops a texture-parameter default from inventing a glTF slot from a
|
|
491
|
+
texture UE Viewer already named as another output. A parameter can exist without being
|
|
492
|
+
wired to its own output, so when its default texture is already the resolved `.mat`'s
|
|
493
|
+
Diffuse (or Normal, and so on) it is a shared placeholder, not a second live slot (Old
|
|
494
|
+
West - VOL 5 Town Props declared an unused `Emissive` default equal to the Fill albedo,
|
|
495
|
+
which painted every dark-wood model with a flat grey emissive wash). Real parameter
|
|
496
|
+
overrides and `.mat`-named Emissive slots still bind, a default whose texture the `.mat`
|
|
497
|
+
left in `Other[]` still recovers its slot by parameter name, and a `.mat` that names no
|
|
498
|
+
slot (the modern converter's `Other[n]` references only) still falls back to the
|
|
499
|
+
collected defaults.
|
|
500
|
+
|
|
501
|
+
Importer version 60 recovers the cut-out of a masked foliage card whose UE Viewer `.mat`
|
|
502
|
+
resolves `Opacity=` to the albedo itself, which has no alpha. When the material references a
|
|
503
|
+
packed `<stem>_AORO` map (AO / roughness / opacity in R / G / B) beside that albedo, the map's
|
|
504
|
+
blue channel becomes the base colour's alpha. Temperate Vegetation: conifer Bushes & Saplings
|
|
505
|
+
had every needle card render as a solid rectangle (Pine_GroundTwig_01 as a full quad with the
|
|
506
|
+
needle atlas painted inside it) because the mask was never applied. The binding is heuristic
|
|
507
|
+
and named in the section's limitations; an opaque material, an unrelated texture stem, or a
|
|
508
|
+
map the pack did not export is left alone.
|
|
509
|
+
|
|
510
|
+
Importer version 59 applies a material instance's base-colour tint vector even when its
|
|
511
|
+
parameter name carries extra words. Old West - VOL 5 Town Props overrides both
|
|
512
|
+
`Albedo Color Tint (Base)` (a bright global multiplier) and `Base Color Tint (Mask)` (the
|
|
513
|
+
dark cloth colour) on a master that tints its albedo through a mask; the exact names missed
|
|
514
|
+
both, so every curtain kept `baseColorFactor` [1,1,1] and rendered the untinted light-grey
|
|
515
|
+
albedo. When both a global base multiplier and a mask colour are overridden, the mask tint is
|
|
516
|
+
the surface colour that shows through. Only an instance's own override is matched broadly, so
|
|
517
|
+
a master's placeholder default (the master's red `Base Color Tint (Mask)`) never paints an
|
|
518
|
+
un-tinted instance; a lone mask tint with no global multiplier (the 03a curtain, whose editor
|
|
519
|
+
mesh thumbnail is the untinted albedo) is left alone, and a `RockTint`-style name with no
|
|
520
|
+
colour token beside `Tint` is left to the graph baker.
|
|
521
|
+
|
|
522
|
+
Importer version 57 gives a mesh the `.mat` / `.props.txt` that sit beside it when two
|
|
523
|
+
packages share an object name (a pack with `MI_Rock_Inst` in two folders bound one
|
|
524
|
+
folder's normal map to the other's meshes); with none beside the mesh the pick is named
|
|
525
|
+
in the section's limitations. The graph bake now evaluates `ObjectScale` (1),
|
|
526
|
+
`PerInstanceRandom` (0.5) and `ObjectPositionWS` (origin) as named heuristic
|
|
527
|
+
approximations and `SplitComponents` exactly, which lets layered cliff-rock masters bake
|
|
528
|
+
their colour texture instead of falling back to neutral grey. The parity S4 check no
|
|
529
|
+
longer lets a 90% colour share hide sections whose graph is known to carry colour.
|
|
530
|
+
|
|
531
|
+
Legacy instance sidecars without override flags retain inherited settings for
|
|
532
|
+
ambiguous opaque, false, and zero defaults. Their non-default values retain the
|
|
533
|
+
previous behavior; missing flags are reported as a material limitation.
|
|
534
|
+
|
|
535
|
+
Importer version 50 reads a bounded authored-material subset from tagged UE4
|
|
536
|
+
object-version 516 packages (legacy -7, UE3 864, licensee 0). Exact source paths
|
|
537
|
+
and closest flagged static overrides select the active branches before reduction.
|
|
538
|
+
Mesh-only package selection still loads needed material parents from the source
|
|
539
|
+
file inventory without exporting unrelated packages; lookups stay within the same
|
|
540
|
+
Content namespace. Mesh import-table material references resolve duplicate source
|
|
541
|
+
basenames by exact object path. Multiple matching references refuse source routing;
|
|
542
|
+
a unique basename fallback reports its unresolved mesh routing.
|
|
543
|
+
Reroutes, scalar constants/parameters and representable multiplication recover
|
|
544
|
+
scalar roughness; glTF normalization retains the raw authored value in the report.
|
|
545
|
+
An authored scalar replaces any earlier roughness texture contribution while
|
|
546
|
+
retaining its packed metallic channel. Source functions, Specular conversion,
|
|
547
|
+
foliage shading and unsupported sampling remain explicit limitations.
|
|
548
|
+
|
|
549
|
+
AO.R is attached only with explicit `Texture2D.SRGB=false`, a serialized linear
|
|
550
|
+
sampler mode (`LinearColor`, `LinearGrayscale` or `Masks`), and source coordinates
|
|
551
|
+
matching the sampler of the currently bound albedo. Its existing glTF coordinate
|
|
552
|
+
mapping is reused only after the selected albedo attaches; an incoming unrelated
|
|
553
|
+
base texture cannot establish that reference. Omitted Unreal defaults remain
|
|
554
|
+
unresolved. Unknown color
|
|
555
|
+
interpretation, ambiguous source paths or exported PNG basenames, nonidentity AO
|
|
556
|
+
multipliers and unproved UV transforms withhold AO, including earlier filename
|
|
557
|
+
guesses. Texture metadata is
|
|
558
|
+
read lazily for the selected AO candidate; unrelated large images are untouched
|
|
559
|
+
by the material reader. Other package profiles keep prior reconstruction with a
|
|
560
|
+
named source-reader limitation. Cached conversions are invalidated.
|
|
561
|
+
|
|
562
|
+
The Buffer reader is independently implemented from the tagged package, property
|
|
563
|
+
and native-input schemas at [CUE4Parse commit b4e9544](https://github.com/FabianFG/CUE4Parse/tree/b4e95441bcf0c975eb3adb68c0fb44c740c2cf62/CUE4Parse/UE4).
|
|
564
|
+
Native expression inputs require verified Core=2 and Framework=33 custom versions;
|
|
565
|
+
tagged instances without native inputs do not require these versions. Native
|
|
566
|
+
trailers and omitted class defaults are outside its scope. This subset does not
|
|
567
|
+
certify original Unreal appearance.
|
|
568
|
+
|
|
377
569
|
Automatic uncooked conversion needs Python 3 with `venv` and `pip`. A Linux
|
|
378
570
|
source build fallback for UE Viewer additionally needs `git`, `g++`, `perl`,
|
|
379
571
|
zlib, and SDL2 development headers. Unreal import currently runs on Linux and
|
|
@@ -460,11 +652,45 @@ texture.type = THREE.UnsignedByteType;
|
|
|
460
652
|
texture.needsUpdate = true;
|
|
461
653
|
```
|
|
462
654
|
|
|
655
|
+
Raw `RGBA16F` textures load through the package's `./data-texture` subpath. The loader
|
|
656
|
+
checks the manifest against the binary (exact length, SHA-256, finite samples) and refuses the
|
|
657
|
+
load, rather than skipping a check, when one cannot run. The source package cannot be fetched
|
|
658
|
+
or re-hashed in a browser, so pin the value the report gives for the texture's source package:
|
|
659
|
+
|
|
660
|
+
```js
|
|
661
|
+
import { loadRgba16fDataTexture } from "threenative-asset-mcp/data-texture";
|
|
662
|
+
|
|
663
|
+
const { texture, manifest } = await loadRgba16fDataTexture(
|
|
664
|
+
new URL(dataTexture.manifest, reportBaseUrl),
|
|
665
|
+
{ expectedSourceSha256: dataTexture.sourceSha256 },
|
|
666
|
+
);
|
|
667
|
+
// texture: THREE.DataTexture, RGBAFormat, HalfFloatType, NoColorSpace, no mipmaps.
|
|
668
|
+
// The stored values are unchanged: negative, HDR, subnormal and alpha bits reach the shader as stored.
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
`loadRgba16fDataTexture` needs an absolute URL and `crypto.subtle`, so serve the page over
|
|
672
|
+
https or from localhost. Its limit is the import's: the texture holds linear half-float
|
|
673
|
+
samples with no decoding, so a material must decide what they mean.
|
|
674
|
+
|
|
463
675
|
Installation-only Unreal `BasicShapes/Plane` references are generated locally
|
|
464
676
|
when the asset pack does not contain that mesh. Rectangular area lights are
|
|
465
677
|
preserved in scene metadata and approximated as punctual point lights because
|
|
466
678
|
glTF's standard punctual-light extension has no area-light type.
|
|
467
679
|
|
|
680
|
+
### Parity sweep and material metadata dumps
|
|
681
|
+
|
|
682
|
+
`npm run parity:fab` imports every owned Fab artifact and scores it against its source package
|
|
683
|
+
(`scorecard.json`). `--export-metadata <dir>` also writes one replayable material dump per pack;
|
|
684
|
+
replay them with `FAB_METADATA_DIR=<dir> npx vitest run tests/fab-metadata.test.ts`. Dumps hold
|
|
685
|
+
licensed pack names and are local-only: never commit them.
|
|
686
|
+
|
|
687
|
+
Set `THREENATIVE_UNREAL_PREVIEW_REFERENCE=/path/to/reference.json` to light the comparison sheets (`parity:fab` and
|
|
688
|
+
`scripts/fidelity-sheet.ts`) with a local version-1 reference profile: radiance and Grid binaries beside the JSON, each
|
|
689
|
+
checked by SHA-256. It is read once per command, and an invalid profile stops the run before any download. Unset, the
|
|
690
|
+
sheets use the approximation and say so. Each picture records what lit it (`identity` in `picture-lighting.json`,
|
|
691
|
+
`sheets/<label>.json` and the scorecard entry). `--resume --sheets` refuses a kept sheet drawn otherwise: resume into a
|
|
692
|
+
new `--out`, or add `--no-sheets`. Profiles and binaries derive from licensed packs: keep them local-only, never commit them.
|
|
693
|
+
|
|
468
694
|
## Configuration
|
|
469
695
|
|
|
470
696
|
| Variable | Default | Purpose |
|
|
@@ -491,8 +717,13 @@ glTF's standard punctual-light extension has no area-light type.
|
|
|
491
717
|
| `AUDIO_CLAP_CHECKPOINT` | unset | Path to the `630k-audioset-best.pt` checkpoint. |
|
|
492
718
|
| `AUDIO_CLAP_CHECKPOINT_SHA256` | unset | Optional pin; a mismatched checkpoint is refused. |
|
|
493
719
|
| `ASSET_DOWNLOAD_DIR` | `~/Downloads/threenative-asset-mcp/assets` | Dedicated directory for direct provider downloads. |
|
|
494
|
-
| `ASSET_MAX_DOWNLOAD_BYTES` | `10737418240` | Maximum
|
|
720
|
+
| `ASSET_MAX_DOWNLOAD_BYTES` | `10737418240` | Maximum bytes per direct file or complete generic acquisition (10 GiB). |
|
|
495
721
|
| `ASSET_DOWNLOAD_TIMEOUT_MS` | `1800000` | Total timeout for one provider download (30 minutes). |
|
|
722
|
+
| `ASSET_ENABLE_KENNEY` | `1` | Bounded official Kenney 3D-pack discovery; `0` disables it. Other values fail initialization. |
|
|
723
|
+
| `ASSET_SEARCH_CONCURRENCY` | `4` | Concurrent provider operations per search; range 1–4. |
|
|
724
|
+
| `ASSET_PROVIDER_TIMEOUT_MS` | `8000` | Provider deadline; range 1–30000 milliseconds. |
|
|
725
|
+
| `ASSET_SEARCH_TIMEOUT_MS` | `10000` | Aggregate search/metadata deadline including queue time; range 1–60000. |
|
|
726
|
+
| `ASSET_PLAN_TTL_MS` | `300000` | Acquisition-plan TTL; range 1–900000; at most 100 bounded plans. |
|
|
496
727
|
| `THREENATIVE_TOOLCHAIN_AUTOINSTALL` | `1` | Set to `0` to disable first-use external-tool installs. |
|
|
497
728
|
| `THREENATIVE_TOOLCHAIN_DIR` | OS cache under `threenative-asset-mcp/toolchain` | UE Viewer, FabCLI, and uncooked-converter cache. |
|
|
498
729
|
| `THREENATIVE_UMODEL_PATH` | auto-detected/provisioned | Absolute path to a UE Viewer executable override. |
|
|
@@ -639,6 +870,32 @@ The MCP host shows no tools
|
|
|
639
870
|
|
|
640
871
|
## Verification
|
|
641
872
|
|
|
873
|
+
GitHub-hosted CI checks pull requests, pushes to main and manual runs on the
|
|
874
|
+
minimum supported Node 20.19 and Node 24. It installs the committed npm lockfile,
|
|
875
|
+
runs typecheck/build, deterministic fixtures and actual built/npm-packed stdio
|
|
876
|
+
consumers. Reports distinguish intentionally conditional proprietary Unreal
|
|
877
|
+
fixtures. No provider live probes, credentials, package publishing or account
|
|
878
|
+
changes run in that workflow. Local CI-equivalent commands are:
|
|
879
|
+
|
|
880
|
+
```bash
|
|
881
|
+
npm ci
|
|
882
|
+
npm run build
|
|
883
|
+
npm run typecheck
|
|
884
|
+
npm run test:ci
|
|
885
|
+
npm run test:smoke
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
The suite needs Chromium (`npx playwright install --with-deps chromium`),
|
|
889
|
+
ffmpeg and Python with NumPy/Pillow for local render/audio fixtures. Live probes
|
|
890
|
+
remain separate from deterministic acceptance tests.
|
|
891
|
+
|
|
892
|
+
Run `npm run doctor` to see which of these your host has. Each missing tool is
|
|
893
|
+
listed with its install command. Locally, a suite whose tool is missing is
|
|
894
|
+
skipped with that command as the reason. With `CI=true` it fails instead.
|
|
895
|
+
Add `--toolchain` to also require what the Unreal importers need on a fresh
|
|
896
|
+
Linux host, or `--toolchain-only` to check only that group.
|
|
897
|
+
|
|
898
|
+
|
|
642
899
|
```bash
|
|
643
900
|
npm ci
|
|
644
901
|
npm run browser:install
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type MetadataContext } from "../discovery/metadata.js";
|
|
1
2
|
export declare const AMBIENTCG_TYPES: readonly ["material", "hdri", "substance", "decal", "atlas", "3d-model", "plain-image", "brush", "terrain", "hdri-element"];
|
|
2
3
|
export type AmbientCgAssetType = (typeof AMBIENTCG_TYPES)[number];
|
|
3
4
|
export interface AmbientCgDownload {
|
|
@@ -39,11 +40,13 @@ export interface AmbientCgCategory {
|
|
|
39
40
|
export declare class AmbientCgClientError extends Error {
|
|
40
41
|
readonly code: "AMBIENTCG_INVALID_INPUT" | "AMBIENTCG_NOT_FOUND" | "AMBIENTCG_RATE_LIMITED" | "AMBIENTCG_UPSTREAM_UNAVAILABLE" | "AMBIENTCG_UPSTREAM_CHANGED";
|
|
41
42
|
readonly retryable: boolean;
|
|
42
|
-
|
|
43
|
+
readonly retryAfter?: string | undefined;
|
|
44
|
+
constructor(code: "AMBIENTCG_INVALID_INPUT" | "AMBIENTCG_NOT_FOUND" | "AMBIENTCG_RATE_LIMITED" | "AMBIENTCG_UPSTREAM_UNAVAILABLE" | "AMBIENTCG_UPSTREAM_CHANGED", message: string, retryable?: boolean, retryAfter?: string | undefined);
|
|
43
45
|
}
|
|
44
46
|
type FetchLike = typeof fetch;
|
|
45
47
|
export declare class AmbientCgClient {
|
|
46
48
|
private readonly fetchImpl;
|
|
49
|
+
private readonly requests;
|
|
47
50
|
constructor(fetchImpl?: FetchLike);
|
|
48
51
|
private request;
|
|
49
52
|
search(input: {
|
|
@@ -53,9 +56,9 @@ export declare class AmbientCgClient {
|
|
|
53
56
|
limit: number;
|
|
54
57
|
offset: number;
|
|
55
58
|
includeDownloads?: boolean;
|
|
56
|
-
}): Promise<AmbientCgSearchResult>;
|
|
57
|
-
getAsset(id: string): Promise<AmbientCgAsset>;
|
|
58
|
-
listCategories(): Promise<AmbientCgCategory[]>;
|
|
59
|
+
}, context?: MetadataContext): Promise<AmbientCgSearchResult>;
|
|
60
|
+
getAsset(id: string, context?: MetadataContext): Promise<AmbientCgAsset>;
|
|
61
|
+
listCategories(context?: MetadataContext): Promise<AmbientCgCategory[]>;
|
|
59
62
|
}
|
|
60
63
|
export {};
|
|
61
64
|
//# sourceMappingURL=client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/ambientcg/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/ambientcg/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,KAAK,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAKhG,eAAO,MAAM,eAAe,YAC1B,UAAU,EACV,MAAM,EACN,WAAW,EACX,OAAO,EACP,OAAO,EACP,UAAU,EACV,aAAa,EACb,OAAO,EACP,SAAS,EACT,cAAc,CACN,CAAC;AACX,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,EAAE,iBAAiB,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,kBAAkB,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,oBAAqB,SAAQ,KAAK;IAE3C,QAAQ,CAAC,IAAI,EACT,yBAAyB,GACzB,qBAAqB,GACrB,wBAAwB,GACxB,gCAAgC,GAChC,4BAA4B;IAEhC,QAAQ,CAAC,SAAS;IAClB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM;IAT9B,YACW,IAAI,EACT,yBAAyB,GACzB,qBAAqB,GACrB,wBAAwB,GACxB,gCAAgC,GAChC,4BAA4B,EAChC,OAAO,EAAE,MAAM,EACN,SAAS,UAAQ,EACjB,UAAU,CAAC,EAAE,MAAM,YAAA,EAI7B;CACF;AAED,KAAK,SAAS,GAAG,OAAO,KAAK,CAAC;AAoI9B,qBAAa,eAAe;IAEd,OAAO,CAAC,QAAQ,CAAC,SAAS;IADtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0B;IACnD,YAA6B,SAAS,GAAE,SAAiB,EAAI;YAE/C,OAAO;IA+Cf,MAAM,CACV,KAAK,EAAE;QACL,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,kBAAkB,CAAC;QAC1B,IAAI,EAAE,SAAS,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;QAC5E,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,gBAAgB,CAAC,EAAE,OAAO,CAAC;KAC5B,EACD,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,qBAAqB,CAAC,CAyChC;IAEK,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,CAajF;IAEK,cAAc,CAAC,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAkBhF;CACF"}
|
package/dist/ambientcg/client.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MetadataRequests, readMetadata } from "../discovery/metadata.js";
|
|
1
2
|
const API_BASE = "https://ambientcg.com/api/v3";
|
|
2
3
|
const TIMEOUT_MS = 20_000;
|
|
3
4
|
export const AMBIENTCG_TYPES = [
|
|
@@ -15,10 +16,12 @@ export const AMBIENTCG_TYPES = [
|
|
|
15
16
|
export class AmbientCgClientError extends Error {
|
|
16
17
|
code;
|
|
17
18
|
retryable;
|
|
18
|
-
|
|
19
|
+
retryAfter;
|
|
20
|
+
constructor(code, message, retryable = false, retryAfter) {
|
|
19
21
|
super(message);
|
|
20
22
|
this.code = code;
|
|
21
23
|
this.retryable = retryable;
|
|
24
|
+
this.retryAfter = retryAfter;
|
|
22
25
|
this.name = "AmbientCgClientError";
|
|
23
26
|
}
|
|
24
27
|
}
|
|
@@ -28,18 +31,13 @@ function record(value) {
|
|
|
28
31
|
: undefined;
|
|
29
32
|
}
|
|
30
33
|
function text(value, max = 12_000) {
|
|
31
|
-
return typeof value === "string" && value.length > 0
|
|
32
|
-
? value.slice(0, max)
|
|
33
|
-
: undefined;
|
|
34
|
+
return typeof value === "string" && value.length > 0 ? value.slice(0, max) : undefined;
|
|
34
35
|
}
|
|
35
36
|
function number(value) {
|
|
36
|
-
return typeof value === "number" && Number.isFinite(value)
|
|
37
|
-
? value
|
|
38
|
-
: undefined;
|
|
37
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
39
38
|
}
|
|
40
39
|
function isType(value) {
|
|
41
|
-
return
|
|
42
|
-
AMBIENTCG_TYPES.includes(value));
|
|
40
|
+
return typeof value === "string" && AMBIENTCG_TYPES.includes(value);
|
|
43
41
|
}
|
|
44
42
|
function normalizeDownload(value) {
|
|
45
43
|
const item = record(value);
|
|
@@ -47,16 +45,27 @@ function normalizeDownload(value) {
|
|
|
47
45
|
const extension = text(item?.extension, 20);
|
|
48
46
|
const url = text(item?.url, 2_048);
|
|
49
47
|
const sizeBytes = number(item?.size);
|
|
50
|
-
if (!attributes ||
|
|
51
|
-
|
|
48
|
+
if (!attributes ||
|
|
49
|
+
!extension ||
|
|
50
|
+
!url ||
|
|
51
|
+
sizeBytes === undefined ||
|
|
52
|
+
!Number.isSafeInteger(sizeBytes) ||
|
|
53
|
+
sizeBytes < 0) {
|
|
54
|
+
throw new AmbientCgClientError("AMBIENTCG_UPSTREAM_CHANGED", "ambientCG returned invalid download metadata.");
|
|
55
|
+
}
|
|
52
56
|
try {
|
|
53
57
|
const parsed = new URL(url);
|
|
54
|
-
if (parsed.protocol !== "https:" ||
|
|
55
|
-
|
|
58
|
+
if (parsed.protocol !== "https:" ||
|
|
59
|
+
parsed.hostname !== "ambientcg.com" ||
|
|
60
|
+
parsed.username ||
|
|
61
|
+
parsed.password ||
|
|
62
|
+
parsed.port ||
|
|
63
|
+
parsed.hash) {
|
|
64
|
+
throw new Error("Unsafe provider file URL.");
|
|
56
65
|
}
|
|
57
66
|
}
|
|
58
67
|
catch {
|
|
59
|
-
|
|
68
|
+
throw new AmbientCgClientError("AMBIENTCG_UPSTREAM_CHANGED", "ambientCG returned an unsafe download URL.");
|
|
60
69
|
}
|
|
61
70
|
return { attributes, extension, url, sizeBytes };
|
|
62
71
|
}
|
|
@@ -69,10 +78,7 @@ function normalizeAsset(value) {
|
|
|
69
78
|
if (!id || !isType(type) || !title || !url) {
|
|
70
79
|
throw new AmbientCgClientError("AMBIENTCG_UPSTREAM_CHANGED", "ambientCG returned invalid asset metadata.");
|
|
71
80
|
}
|
|
72
|
-
const descriptions = [
|
|
73
|
-
text(item?.longDescription),
|
|
74
|
-
text(item?.shortDescription),
|
|
75
|
-
];
|
|
81
|
+
const descriptions = [text(item?.longDescription), text(item?.shortDescription)];
|
|
76
82
|
const description = descriptions.find((entry) => entry !== undefined);
|
|
77
83
|
const statistics = record(item?.downloadStatistics);
|
|
78
84
|
const dimensions = record(item?.dimensions);
|
|
@@ -80,6 +86,8 @@ function normalizeAsset(value) {
|
|
|
80
86
|
const thumbnailUrl = text(thumbnails?.["512-WEBP"], 2_048) ??
|
|
81
87
|
text(thumbnails?.["512-PNG"], 2_048) ??
|
|
82
88
|
text(thumbnails?.["256-WEBP"], 2_048);
|
|
89
|
+
if (Array.isArray(item?.downloads) && item.downloads.length > 500)
|
|
90
|
+
throw new AmbientCgClientError("AMBIENTCG_UPSTREAM_CHANGED", "ambientCG download variants exceed the entry limit.");
|
|
83
91
|
const downloads = Array.isArray(item?.downloads)
|
|
84
92
|
? item.downloads
|
|
85
93
|
.map(normalizeDownload)
|
|
@@ -105,12 +113,8 @@ function normalizeAsset(value) {
|
|
|
105
113
|
: [],
|
|
106
114
|
downloads,
|
|
107
115
|
...(description ? { description } : {}),
|
|
108
|
-
...(text(item?.releaseDate, 20)
|
|
109
|
-
|
|
110
|
-
: {}),
|
|
111
|
-
...(text(item?.technique, 100)
|
|
112
|
-
? { technique: text(item?.technique, 100) }
|
|
113
|
-
: {}),
|
|
116
|
+
...(text(item?.releaseDate, 20) ? { releaseDate: text(item?.releaseDate, 20) } : {}),
|
|
117
|
+
...(text(item?.technique, 100) ? { technique: text(item?.technique, 100) } : {}),
|
|
114
118
|
...(number(statistics?.total) !== undefined
|
|
115
119
|
? { downloadCount: number(statistics?.total) }
|
|
116
120
|
: {}),
|
|
@@ -131,37 +135,44 @@ function normalizeAsset(value) {
|
|
|
131
135
|
}
|
|
132
136
|
export class AmbientCgClient {
|
|
133
137
|
fetchImpl;
|
|
138
|
+
requests = new MetadataRequests();
|
|
134
139
|
constructor(fetchImpl = fetch) {
|
|
135
140
|
this.fetchImpl = fetchImpl;
|
|
136
141
|
}
|
|
137
|
-
async request(path) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
response
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
142
|
+
async request(path, context = {}) {
|
|
143
|
+
return this.requests.run(path, context.signal, async (callerSignal) => {
|
|
144
|
+
const signal = AbortSignal.any([callerSignal, AbortSignal.timeout(TIMEOUT_MS)]);
|
|
145
|
+
let response;
|
|
146
|
+
try {
|
|
147
|
+
response = await this.fetchImpl(`${API_BASE}${path}`, {
|
|
148
|
+
headers: {
|
|
149
|
+
accept: "application/json",
|
|
150
|
+
"user-agent": "threenative-asset-mcp/0.4.0",
|
|
151
|
+
},
|
|
152
|
+
signal,
|
|
153
|
+
redirect: "error",
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
throw new AmbientCgClientError("AMBIENTCG_UPSTREAM_UNAVAILABLE", "ambientCG could not be reached.", true);
|
|
158
|
+
}
|
|
159
|
+
if (!response.ok)
|
|
160
|
+
await response.body?.cancel();
|
|
161
|
+
if (response.status === 429) {
|
|
162
|
+
throw new AmbientCgClientError("AMBIENTCG_RATE_LIMITED", "ambientCG rate-limited the request.", true, response.headers.get("retry-after")?.slice(0, 100));
|
|
163
|
+
}
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
throw new AmbientCgClientError("AMBIENTCG_UPSTREAM_UNAVAILABLE", `ambientCG returned HTTP ${response.status}.`, response.status >= 500);
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
return JSON.parse(await readMetadata(response, 16 * 1024 * 1024, signal));
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
throw new AmbientCgClientError("AMBIENTCG_UPSTREAM_CHANGED", "ambientCG returned invalid JSON.");
|
|
172
|
+
}
|
|
173
|
+
});
|
|
163
174
|
}
|
|
164
|
-
async search(input) {
|
|
175
|
+
async search(input, context = {}) {
|
|
165
176
|
const params = new URLSearchParams({
|
|
166
177
|
sort: input.sort,
|
|
167
178
|
limit: String(input.limit),
|
|
@@ -185,10 +196,10 @@ export class AmbientCgClient {
|
|
|
185
196
|
params.set("q", input.query);
|
|
186
197
|
if (input.type)
|
|
187
198
|
params.set("type", input.type);
|
|
188
|
-
const payload = record(await this.request(`/assets?${params}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
199
|
+
const payload = record(await this.request(`/assets?${params}`, context));
|
|
200
|
+
if (Array.isArray(payload?.assets) && payload.assets.length > 1000)
|
|
201
|
+
throw new AmbientCgClientError("AMBIENTCG_UPSTREAM_CHANGED", "ambientCG asset list exceeds its entry limit.");
|
|
202
|
+
const assets = Array.isArray(payload?.assets) ? payload.assets.map(normalizeAsset) : undefined;
|
|
192
203
|
const total = number(payload?.totalResults);
|
|
193
204
|
if (!assets || total === undefined) {
|
|
194
205
|
throw new AmbientCgClientError("AMBIENTCG_UPSTREAM_CHANGED", "ambientCG returned an invalid search result.");
|
|
@@ -196,26 +207,24 @@ export class AmbientCgClient {
|
|
|
196
207
|
return {
|
|
197
208
|
assets,
|
|
198
209
|
total,
|
|
199
|
-
...(input.offset + assets.length < total
|
|
200
|
-
? { nextOffset: input.offset + assets.length }
|
|
201
|
-
: {}),
|
|
210
|
+
...(input.offset + assets.length < total ? { nextOffset: input.offset + assets.length } : {}),
|
|
202
211
|
};
|
|
203
212
|
}
|
|
204
|
-
async getAsset(id) {
|
|
213
|
+
async getAsset(id, context = {}) {
|
|
205
214
|
const params = new URLSearchParams({
|
|
206
215
|
id,
|
|
207
216
|
limit: "1",
|
|
208
217
|
include: "type,releaseDate,shortDescription,longDescription,title,url,tags,dimensions,downloadStatistics,downloads,technique,maps,thumbnails",
|
|
209
218
|
});
|
|
210
|
-
const payload = record(await this.request(`/assets?${params}
|
|
219
|
+
const payload = record(await this.request(`/assets?${params}`, context));
|
|
211
220
|
const first = Array.isArray(payload?.assets) ? payload.assets[0] : undefined;
|
|
212
221
|
if (!first) {
|
|
213
222
|
throw new AmbientCgClientError("AMBIENTCG_NOT_FOUND", "The ambientCG asset was not found.");
|
|
214
223
|
}
|
|
215
224
|
return normalizeAsset(first);
|
|
216
225
|
}
|
|
217
|
-
async listCategories() {
|
|
218
|
-
const payload = await this.request("/categories");
|
|
226
|
+
async listCategories(context = {}) {
|
|
227
|
+
const payload = await this.request("/categories", context);
|
|
219
228
|
if (!Array.isArray(payload)) {
|
|
220
229
|
throw new AmbientCgClientError("AMBIENTCG_UPSTREAM_CHANGED", "ambientCG returned an invalid category list.");
|
|
221
230
|
}
|