UAsset Reference MCP v0.3.2 fixes a gap that appears as soon as a Unity project becomes modular. A game can depend on a local Unity Package Manager package through Packages/manifest.json, while that package’s source folder lives outside the Unity project directory. Unity imports it, resolves its GUIDs, and lets project assets reference it. A project-root-only asset scan can still miss it.
The change in v0.3.2 is automatic external local UPM package indexing. The indexer now discovers active local directory packages from Unity package metadata, reads their files from their physical source folders, and stores graph nodes under canonical Unity paths such as Packages/com.company.gameplay/Runtime/EnemyConfig.asset. The SQLite graph stays portable, and traversal still works through GUID edges rather than through filesystem assumptions.
The Missing Root Is Not Optional
Unity projects often split shared gameplay, UI, networking, or tools code into UPM packages. During development, those packages are commonly referenced as local directories so the package can be edited alongside the game.
The Unity manifest might look like this:
{
"dependencies": {
"com.company.gameplay": "file:../../modules/com.company.gameplay"
}
}
From Unity’s point of view, assets inside that module are part of the active project package set. A prefab in Assets/Characters/Enemy.prefab can serialize a reference to a ScriptableObject inside com.company.gameplay, and Unity resolves the reference through the target asset’s .meta GUID.
A scanner that only walks the project root has a different view. It can see Assets/, embedded packages under Packages/, and cached registry packages under Library/PackageCache/. It cannot see ../../modules/com.company.gameplay unless it understands Unity’s package metadata. The result is an unresolved GUID even though the Unity Editor can resolve the same asset.
v0.3.2 makes the scan scope match the active Unity package graph more closely:
Unity project root
Assets/ scanned as project assets
Packages/com.embedded.tool/ scanned as embedded package assets
Library/PackageCache/... scanned as cached package assets
External local package
../../modules/com.company.gameplay/
scanned as package assets
stored as Packages/com.company.gameplay/...
The indexer is still static and offline. It does not launch Unity or ask the Editor for a package list. It reads the metadata Unity already writes, then builds the graph from files on disk.
Physical Sources Become Unity Paths
The central model is physical root versus virtual path. The physical root is where the indexer reads bytes. The virtual path is the Unity path stored in the graph.
For an external package named com.company.gameplay, the physical source might be:
/Users/vincewang/modules/com.company.gameplay/Runtime/EnemyConfig.asset
The graph stores it as:
Packages/com.company.gameplay/Runtime/EnemyConfig.asset
That stored path is the important part. Absolute machine paths are local development details. If the index persisted /Users/... or ../../modules/..., a shared snapshot would describe one developer’s folder layout instead of the Unity project model. Packages/<package-name>/... is the path Unity users expect when talking about package assets, so it is the path the database records.
The node still carries package identity:
path: Packages/com.company.gameplay/Runtime/EnemyConfig.asset
origin: package
package_id: com.company.gameplay
Reference extraction does not need a separate package traversal mode. Unity serialized references point to GUIDs, and the index resolves GUIDs across all active roots. A project asset can reference a package asset. A package asset can reference a project asset. Two packages can reference each other. Once the GUID map includes every active root, the edge model remains the same.
Discovery Follows Unity Metadata
The scanner builds a package-source plan before walking files. It reads Packages/manifest.json for direct dependencies and Packages/packages-lock.json when Unity has written resolved package metadata.
The manifest is the authority for direct dependency declarations. A direct local dependency using a file: directory can be discovered from the manifest alone. Relative file: paths resolve from the project’s Packages/ directory, matching how Unity interprets those paths, not from whichever directory happened to launch the CLI.
The lockfile can add resolved local-package information, including local transitive packages. That matters when a project pulls in a package that itself depends on another local package. The indexer can include the package Unity resolved without asking the user to configure extra scan roots.
Discovery is intentionally narrow. The indexer follows package paths explicitly declared or resolved by Unity project metadata. It does not crawl parent folders, scan sibling repositories, expand arbitrary workspace directories, read environment variables, download missing packages, or treat local .tgz files and file:// Git URLs as external directories. Those sources remain Unity package-resolution concerns, and cached package content continues to be handled through Library/PackageCache/ when present.
Each external package candidate has to be an accessible directory with a parseable package.json. The package manifest’s name must match the dependency name declared by the project. A mismatch is skipped with a package-discovery warning instead of being indexed under the wrong Unity path.
Precedence Prevents Duplicate Package Views
One package name must map to one active source. Otherwise the graph could index both a live local package and a stale cached copy of the same package, creating duplicate nodes and confusing dependency answers.
v0.3.2 uses deterministic source precedence:
1. Embedded package under Packages/<name>
2. Active external local package from manifest or lock metadata
3. Matching package cache under Library/PackageCache
An embedded package wins because it is physically inside the Unity project and overrides package-manager resolution for that package name. An active external local package wins over a matching cache entry because the local source is the package Unity is using for development. The cache remains useful for registry and Git packages, but it should not compete with the current local source.
The existing duplicate-GUID validation remains the final safety boundary. If two genuinely active assets claim the same Unity GUID, indexing fails before publishing a new database. External package discovery does not silently choose a GUID winner, because Unity asset identity has to stay unambiguous for graph answers to be trustworthy.
Incremental Refresh Tracks Package Selection
Normal indexing is still incremental. Each logical asset uses the newer modification time of the asset file and its sibling .meta, including assets inside external packages. Editing a package asset or its .meta therefore participates in the same incremental path as project assets.
Package selection introduces another freshness problem. The manifest or lockfile can change while the selected package assets appear unchanged. A dependency can be removed, pointed at another folder, or replaced with a different source that preserves the same virtual path, GUID, and timestamps. A file-level timestamp check is not enough to detect that package-source decision.
v0.3.2 records a package-discovery fingerprint for the indexed package plan. The fingerprint is derived from the project manifest, lockfile, and selected package-source descriptors. When that fingerprint changes, the next index run discards the copied incremental staging database and performs a fresh candidate-graph reconciliation before the atomic publish step.
That gives the indexer two layers of freshness. Asset and .meta mtimes handle ordinary edits. The package-discovery fingerprint handles changes to which package sources are active. force: true remains the explicit guaranteed-freshness path and scans all active roots unconditionally.
unity-asset-reference-mcp-index index /path/to/UnityProject --force
MCP callers use the same public tool as before:
index_project(path, force?)
There is no new scan-root argument. The point of this release is that active local UPM packages are project metadata, so indexing them should not require a second configuration system.
Warnings Are Bounded and Localized
Package discovery can fail for reasons that should not make the whole project impossible to index. A package directory may be missing on one machine. A local path may be inaccessible. A package.json file may be malformed. A package may declare a different name than the dependency key in the Unity manifest.
Those cases produce package-discovery warnings and skip the invalid source. Other valid roots continue to index. The previous good index is still replaced only after the complete candidate graph passes the existing validation and publication checks.
A missing or malformed project manifest disables external-local discovery for that run and emits one bounded manifest warning. Embedded package and package-cache scanning still continue, so the indexer preserves as much useful graph data as it can without inventing package state.
Ignore rules use canonical paths, not physical source paths. A configured ignore such as Packages/com.company.gameplay/Samples~/** applies to the external package as it appears in Unity. Root-level ignores and descendant ignores are evaluated before recursion, so an ignored package root produces neither nodes nor per-file warning noise.
The Release Changes Scope, Not the Public Shape
v0.3.2 expands what the existing indexer can see. It does not change the public query surface.
The npm package was published as unity-asset-reference-mcp@0.3.2, and the annotated v0.3.2 tag points at the published release commit. The Unity verification package stays at 0.2.0. SQLite remains schema 3. MCP and CLI inputs and successful response shapes are unchanged.
The release validation covered the package-indexing behavior and the publication boundary. The release evidence records root Vitest passing across 39 test files and 298 tests, npm run typecheck, npm run build, package dry-runs for both the root package and Unity package, git diff --check, a successful GitHub Actions release workflow, and npm registry verification returning exactly 0.3.2.
The exclusions are as important as the feature. v0.3.2 does not add manual root configuration, filesystem watchers, Unity Editor callbacks, package downloads, local package mutation, schema migration, or new MCP tools. It reads the active local packages Unity already knows about, represents them with Unity’s virtual package paths, and lets the existing GUID graph answer cross-boundary dependency questions.
External local package indexing makes the graph match modular Unity projects more closely. Package assets no longer disappear just because their source folder sits outside the game repository. The index still publishes portable SQLite data, still resolves by GUID, and still keeps package discovery explicit enough to warn when local machine state does not match the project’s package declarations.