Sign In

unity-mcp-server

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

unity-mcp-server - npm Package Compare versions

Comparing version
1.6.1
to
1.7.0
+319
dist/readers/unity6.js
/**
* Unity 6.x coverage readers — multiplayer, build profiles, physics, UGS,
* Adaptive Performance, accessibility, Cinemachine, platform hints, BIRP migration.
* Filesystem / manifest only (no Editor).
*/
import { existsSync, readdirSync } from "node:fs";
import { join } from "node:path";
import { ASSETS, PROJECT_SETTINGS, PACKAGES, listFilesRecursive, readFileSafe, readJsonSafe, resolveUnderRoot, } from "./helpers.js";
import { getPackages, getUnityVersion, getBuildTargetInfo, getGraphicsSettings, getPhysicsSettings } from "./project.js";
function packageHits(root, test) {
return getPackages(root)
.dependencies.filter((d) => test(d.name))
.map((d) => ({ package: d.name, version: d.version }));
}
function scanScripts(root, rules, maxFiles = 400) {
const scripts = listFilesRecursive(root, ASSETS, { ext: ".cs" }).slice(0, maxFiles);
const byLabel = {};
const samples = [];
for (const rule of rules)
byLabel[rule.label] = 0;
for (const path of scripts) {
const content = readFileSafe(root, path);
if (!content)
continue;
const labels = [];
for (const rule of rules) {
if (rule.re.test(content)) {
byLabel[rule.label]++;
labels.push(rule.label);
}
}
if (labels.length)
samples.push({ path, labels });
}
return {
by_label: Object.entries(byLabel).map(([label, count]) => ({ label, count })),
sample_scripts: samples.slice(0, 25),
};
}
/** Netcode for GameObjects, Unity Multiplayer, Relay/Lobby/Vivox, Mirror/FishNet, etc. */
export function getMultiplayerStack(root) {
const packages = packageHits(root, (n) => n.includes("netcode") ||
n.includes("multiplayer") ||
n.includes("transport") ||
n.includes("relay") ||
n.includes("lobby") ||
n.includes("vivox") ||
n.includes("dedicated-server") ||
n.includes("services.multiplayer") ||
n.includes("mirror") ||
n.includes("fishnet") ||
n.includes("photon"));
const scripts = scanScripts(root, [
{ label: "Netcode for GameObjects", re: /Unity\.Netcode|NetworkBehaviour|NetworkObject|NetworkManager/i },
{ label: "Unity Transport / UTP", re: /Unity\.Networking\.Transport|NetworkDriver/i },
{ label: "Relay / Lobby", re: /Unity\.Services\.Relay|Unity\.Services\.Lobby/i },
{ label: "Mirror / FishNet / Photon", re: /\bMirror\.|FishNet\.|Photon\.Pun|PhotonNetwork/i },
]);
const hints = [];
if (!packages.length && !scripts.sample_scripts.length) {
hints.push("No multiplayer packages or Netcode scripts detected. If this is an online game, add com.unity.netcode.gameobjects or confirm a custom stack.");
}
return { packages, ...scripts, hints };
}
/** Unity 6 Build Profile assets (and classic EditorBuildSettings as fallback context). */
export function listBuildProfiles(root) {
const candidates = [
...listFilesRecursive(root, ASSETS, { ext: ".asset" }),
...listFilesRecursive(root, PROJECT_SETTINGS, { ext: ".asset" }),
];
const profiles = [];
for (const path of candidates) {
const lower = path.toLowerCase();
const content = readFileSafe(root, path);
if (!content)
continue;
const looksLike = content.includes("BuildProfile:") ||
(lower.includes("buildprofile") && (content.includes("m_BuildTarget:") || content.includes("m_Name:")));
if (!looksLike)
continue;
const name = content.match(/m_Name:\s*([^\n]+)/)?.[1]?.trim() ??
path.replace(/^.*\//, "").replace(/\.asset$/, "");
const hints = [];
if (content.includes("m_ScriptingDefines") || content.includes("scriptingDefines"))
hints.push("has_scripting_defines");
if (content.includes(".unity"))
hints.push("may_embed_scene_list");
if (content.includes("m_PlayerSettings") || content.includes("PlayerSettings"))
hints.push("has_player_settings_override");
profiles.push({ path, name, hints });
}
const seen = new Set();
const unique = profiles.filter((p) => (seen.has(p.path) ? false : (seen.add(p.path), true)));
return {
profiles: unique.slice(0, 50),
count: unique.length,
classic_editor_build_settings: existsSync(join(root, PROJECT_SETTINGS, "EditorBuildSettings.asset")),
hints: unique.length === 0
? [
"No BuildProfile assets found. Project may still use classic EditorBuildSettings only (pre–Unity 6 Build Profiles workflow).",
]
: [],
};
}
/** Physics modules + Unity Physics / Physics Core 2D package presence. */
export function getPhysicsStack(root) {
const packages = packageHits(root, (n) => n.includes("physics") ||
n.includes("havok") ||
n.includes("u2d.physics") ||
n.includes("lowlevelphysics"));
const modulesManifest = readJsonSafe(root, PACKAGES, "manifest.json");
const deps = modulesManifest?.dependencies ?? {};
const settings = getPhysicsSettings(root);
const scripts = scanScripts(root, [
{ label: "Unity Physics / ECS physics", re: /Unity\.Physics|ICollisionEventsJob/i },
{ label: "Physics Core 2D / LowLevelPhysics2D", re: /Unity\.U2D\.Physics|LowLevelPhysics2D|PhysicsCore2D/i },
{ label: "Classic Rigidbody / Collider", re: /\bRigidbody\b|\bCollider\b|Physics\.Raycast/i },
]);
return {
packages,
project_physics_settings: {
dynamics_keys: Object.keys(settings.dynamics ?? {}).slice(0, 25),
physics2d_keys: Object.keys(settings.physics2d ?? {}).slice(0, 25),
},
modules_hint: {
physics3d_in_manifest: "com.unity.modules.physics" in deps,
physics2d_in_manifest: "com.unity.modules.physics2d" in deps,
note: "Built-in modules may be enabled without appearing in manifest; check Package Manager → Built-in for disables (Unity 6.4+ build-size tip).",
},
...scripts,
};
}
/** Adaptive Performance + Project Auditor packages and assets. */
export function getOptimizationPackageInventory(root) {
const packages = packageHits(root, (n) => n.includes("adaptiveperformance") ||
n.includes("adaptive-performance") ||
n.includes("project-auditor") ||
n.includes("projectauditor"));
const apSettings = listFilesRecursive(root, ASSETS, { ext: ".asset" })
.filter((p) => {
const lower = p.toLowerCase();
if (!lower.includes("adaptive") && !lower.includes("auditor"))
return false;
const c = readFileSafe(root, p);
return !!(c && (c.includes("AdaptivePerformance") || c.includes("ProjectAuditor") || c.includes("Scaler")));
})
.slice(0, 30);
const scripts = scanScripts(root, [
{ label: "Adaptive Performance", re: /UnityEngine\.AdaptivePerformance|IAdaptivePerformance|AdaptivePerformanceManager/i },
{ label: "Project Auditor APIs", re: /Unity\.ProjectAuditor|ProjectAuditor/i },
]);
return { packages, settings_or_assets: apSettings, ...scripts };
}
/** Unity Gaming Services / Cloud Code / Authentication / Economy / etc. */
export function getUgsCloudStack(root) {
const packages = packageHits(root, (n) => n.startsWith("com.unity.services.") || n.includes("cloud-code") || n.includes("cloudcode"));
const scripts = scanScripts(root, [
{ label: "Unity Services Core / Auth", re: /Unity\.Services\.Core|Unity\.Services\.Authentication|UnityServices\.InitializeAsync/i },
{ label: "Cloud Code", re: /Unity\.Services\.CloudCode|CloudCodeService/i },
{ label: "Economy / Remote Config / Friends", re: /Unity\.Services\.Economy|Unity\.Services\.RemoteConfig|Unity\.Services\.Friends/i },
{ label: "Cloud Save / Leaderboards", re: /Unity\.Services\.CloudSave|Unity\.Services\.Leaderboards/i },
]);
const projectIdHints = [];
const cloud = readFileSafe(root, PROJECT_SETTINGS, "UnityConnectSettings.asset");
if (cloud) {
const org = cloud.match(/m_OrganizationId:\s*([^\n]+)/)?.[1]?.trim();
const pid = cloud.match(/m_ProjectId:\s*([^\n]+)/)?.[1]?.trim();
if (org)
projectIdHints.push(`organization:${org}`);
if (pid)
projectIdHints.push(`project:${pid}`);
}
return { packages, unity_connect_hints: projectIdHints, ...scripts };
}
/** Accessibility module / package + script usage. */
export function getAccessibilityStack(root) {
const packages = packageHits(root, (n) => n.includes("accessibility") || n === "com.unity.modules.accessibility");
const scripts = scanScripts(root, [
{ label: "Accessibility APIs", re: /UnityEngine\.Accessibility|AccessibilityNode|AssistiveSupport|AccessibilityRole/i },
]);
return { packages, ...scripts };
}
/** Cinemachine virtual cameras and related assets. */
export function listCinemachineAssets(root) {
const packages = packageHits(root, (n) => n.includes("cinemachine"));
const assets = listFilesRecursive(root, ASSETS, { ext: ".asset" })
.filter((p) => {
const lower = p.toLowerCase();
if (lower.includes("cinemachine") || lower.includes("vcam") || lower.includes("virtualcamera"))
return true;
const c = readFileSafe(root, p);
return !!(c && (c.includes("CinemachineCamera") || c.includes("CinemachineVirtualCamera") || c.includes("CinemachineBrain")));
})
.slice(0, 40);
const prefabs = listFilesRecursive(root, ASSETS, { ext: ".prefab" })
.filter((p) => {
const c = readFileSafe(root, p);
return !!(c && c.includes("Cinemachine"));
})
.slice(0, 30);
return { packages, assets, prefabs_with_cinemachine: prefabs, count: assets.length + prefabs.length };
}
/** Platform / PlayerSettings hints for Web, Android, DirectStorage-era Windows, etc. */
export function getPlatformBuildHints(root) {
const unityVersion = getUnityVersion(root);
const buildTarget = getBuildTargetInfo(root);
const ps = readFileSafe(root, PROJECT_SETTINGS, "ProjectSettings.asset") ?? "";
const kv = {};
const pick = (key, re) => {
const m = ps.match(re);
if (m?.[1])
kv[key] = m[1].trim();
};
pick("activeInputHandler", /activeInputHandler:\s*(\d+)/);
pick("androidMinSdkVersion", /AndroidMinSdkVersion:\s*(\d+)/);
pick("androidTargetSdkVersion", /AndroidTargetSdkVersion:\s*(\d+)/);
pick("webGLMemorySize", /webGLMemorySize:\s*(\d+)/);
pick("webGLExceptionSupport", /webGLExceptionSupport:\s*(\d+)/);
pick("webGLThreadsSupport", /webGLThreadsSupport:\s*(\d+)/);
pick("runInBackground", /runInBackground:\s*(\d+)/);
pick("fullscreenMode", /fullscreenMode:\s*(\d+)/);
const backendBlock = ps.match(/scriptingBackend:[\s\S]{0,400}/);
const packages = packageHits(root, (n) => n.includes("webgl") ||
n.includes("mobile") ||
n.includes("android") ||
n.includes("ios") ||
n.includes("visionos") ||
n.includes("dedicated-server"));
const hints = [];
if (kv.webGLThreadsSupport === "1") {
hints.push("WebGL threads support enabled (Burst jobs on Web possible in Unity 6.4+).");
}
if (kv.androidMinSdkVersion && Number(kv.androidMinSdkVersion) > 0) {
hints.push(`Android min SDK ${kv.androidMinSdkVersion} — confirm against Unity 6.x platform requirements.`);
}
return {
unity_version: unityVersion,
build_target: buildTarget,
player_settings_snippets: kv,
scripting_backend_snippet: backendBlock?.[0]?.slice(0, 300) ?? null,
related_packages: packages,
hints,
};
}
/** Detect Built-in RP vs URP/HDRP for migration risk (BIRP deprecated in 6.5). */
export function getRenderPipelineMigrationHints(root) {
const packages = getPackages(root).dependencies;
const hasUrp = packages.some((p) => p.name.includes("render-pipelines.universal"));
const hasHdrp = packages.some((p) => p.name.includes("render-pipelines.high-definition"));
const hasCoreRp = packages.some((p) => p.name.includes("render-pipelines.core"));
const graphics = getGraphicsSettings(root);
const graphicsRaw = readFileSafe(root, PROJECT_SETTINGS, "GraphicsSettings.asset") ?? "";
const customRp = graphicsRaw.match(/m_CustomRenderPipeline:\s*\{[^}]*guid:\s*([a-f0-9]{32})/i)?.[1];
const hasCustomPipelineRef = !!customRp && customRp !== "00000000000000000000000000000000";
let likely = "mixed_or_unknown";
if (hasHdrp && !hasUrp)
likely = "hdrp";
else if (hasUrp && !hasHdrp)
likely = "urp";
else if (hasUrp && hasHdrp)
likely = "mixed_or_unknown";
else if (!hasCustomPipelineRef && !hasCoreRp)
likely = "birp";
else if (hasCustomPipelineRef)
likely = "mixed_or_unknown";
const hints = [];
if (likely === "birp") {
hints.push("Likely Built-in Render Pipeline. BIRP is deprecated in Unity 6.5 and will be obsolete after 6.7 LTS — plan URP/HDRP migration.");
}
if (hasUrp && hasHdrp)
hints.push("Both URP and HDRP packages present — confirm intentional dual-pipeline setup.");
const pipelineAssets = listFilesRecursive(root, ASSETS, { ext: ".asset" })
.filter((p) => {
const c = readFileSafe(root, p);
return !!(c &&
(c.includes("UniversalRenderPipelineAsset") ||
c.includes("HDRenderPipelineAsset") ||
c.includes("RenderPipelineAsset")));
})
.slice(0, 20);
return {
likely_pipeline: likely,
packages: packages
.filter((p) => p.name.includes("render-pipelines") || p.name.includes("shadergraph") || p.name.includes("visualeffect"))
.map((p) => ({ package: p.name, version: p.version })),
custom_render_pipeline_guid: customRp ?? null,
pipeline_assets: pipelineAssets,
graphics_settings_sample_keys: Object.keys(graphics).slice(0, 20),
hints,
};
}
/** Localization package + tables. */
export function getLocalizationStack(root) {
const packages = packageHits(root, (n) => n.includes("localization"));
const tablesDir = join(ASSETS, "Localization");
const tableFiles = [];
const full = resolveUnderRoot(root, tablesDir);
if (full) {
try {
for (const e of readdirSync(full)) {
if (/\.(asset|csv|json)$/i.test(e))
tableFiles.push(join(tablesDir, e).split(/[/\\]/).join("/"));
}
}
catch {
/* */
}
}
const localeAssets = listFilesRecursive(root, ASSETS, { ext: ".asset" })
.filter((p) => {
const lower = p.toLowerCase();
if (lower.includes("locale") || lower.includes("localization") || lower.includes("stringtable"))
return true;
const c = readFileSafe(root, p);
return !!(c && (c.includes("Locale:") || c.includes("StringTable") || c.includes("LocalizationSettings")));
})
.slice(0, 40);
return { packages, tables_dir_files: tableFiles, locale_related_assets: localeAssets };
}
---
name: unity-2d-tilemap-sprites
description: >-
Use for 2D art pipelines: sprite atlases, sprite-mode textures, tilemaps,
and import settings (PPU, maxSize).
---
# 2D tilemaps & sprites
## Discovery
1. `list_sprite_atlases`
2. `list_sprite_assets` — textures with spriteMode
3. `list_tilemap_assets`
4. `get_texture_meta` — PPU, maxSize, spriteMode
5. `get_feature_set_inference` — 2D feature set packages
## Checklist
- Pack atlases by frequency of co-draw; watch padding/bleeding.
- Consistent PPU across characters/tiles.
- Compress atlases per platform; avoid uncompressed RGBA on mobile.
- Tilemaps: separate collision/decoration layers.
## Related
- `list_materials`, `unity-build-size-optimization`
---
name: unity-ai-audit
description: >-
Use to audit a Unity project's AI/ML stack: packages, models, scripts,
configs, and release risks. Start here for "what AI does this game use?"
---
# Unity AI stack audit
## One-shot
Run `get_ai_stack_summary` with `include_prompt_assets: true`.
## Deep dive order
| Step | Tool | Why |
|------|------|-----|
| 1 | `get_ai_ml_package_inventory` | Official Unity AI packages & versions |
| 2 | `list_ml_model_assets` | ONNX and other models in repo |
| 3 | `list_ml_agents_training_configs` | RL training YAMLs |
| 4 | `find_ai_related_scripts` | Code-level AI APIs |
| 5 | `list_ai_prompt_or_config_assets` | Prompt/RAG JSON or docs |
| 6 | `get_build_size_estimate` | Model weight in shipping build |
| 7 | `get_release_readiness` | Broken refs + cycles + large assets |
## Bundled skills
Call `list_ai_skills` (returns `category`) then `read_ai_skill`.
Skills follow Unity Editor domains — see `skills/README.md`. Highlights:
- **Scenes:** `unity-scenes-workflow`
- **Model-agnostic:** `unity-model-agnostic-inference`
- **Vendor-specific:** `unity-ml-agents`, `unity-sentis-inference`, `unity-llm-integration`
- **Gameplay AI:** `unity-navmesh-npc`
- **Performance:** `unity-runtime-performance`, `unity-build-size-optimization`, `unity-memory-asset-pressure`
## Report template
```markdown
## AI stack summary
- Packages: ...
- Models: N ONNX, ...
- Scripts: N files (top labels: ...)
```
---
name: unity-llm-integration
description: >-
Use when adding LLM/chat/RAG features to a Unity game or tool: API keys,
prompt assets, safety, latency, and server-side vs client-side calls.
---
# Unity LLM integration
## Discovery
1. `find_ai_related_scripts` — label **LLM / chat APIs**
2. `list_ai_prompt_or_config_assets` — JSON/MD with prompt-like keys under Assets
3. `get_ai_stack_summary` with `include_prompt_assets: true`
## Security & architecture
- **Never** commit API keys; use environment variables, Unity Cloud Code, or your backend proxy.
- Prefer **server-mediated** LLM calls for shipping titles (rate limits, moderation, cost).
- For editor-only tools, ScriptableObject prompts are OK if no secrets are embedded.
## UX patterns
- Stream responses to UI (TMP) with cancellation tokens.
- Cache embeddings locally only when license/privacy allows.
- Cap token usage per session for live games.
## Audit workflow
1. `find_ai_related_scripts` for hardcoded endpoints
2. `search_project` with `script_pattern: OpenAI` or `Anthropic`
3. Review `list_ai_prompt_or_config_assets` for PII in prompts
---
name: unity-ml-agents
description: >-
Use when working on Unity ML-Agents: training configs, BehaviorParameters,
sensors, actuators, inference in builds, or com.unity.ml-agents packages.
---
# Unity ML-Agents
## Discovery (unity-mcp-server)
1. `get_ai_stack_summary` — packages, ONNX assets, script hits, training YAMLs
2. `get_ai_ml_package_inventory` — confirm `com.unity.ml-agents` version
3. `list_ml_agents_training_configs` — trainer YAML paths
4. `find_ai_related_scripts` — filter label **ML-Agents**
## Implementation checklist
- **Training**: YAML defines `behaviors`, `trainer_type`, hyperparameters; keep configs in repo root or `config/`.
- **Inference**: Prefabs/scenes need `BehaviorParameters` + trained `.onnx` in `Model` field; verify with `list_prefabs_with_component` for `BehaviorParameters` if serialized as component name.
- **Sensors/Observations**: Stack vector observations consistently between training and runtime.
- **Performance**: Prefer inference on worker thread; cap decision frequency for mobile.
## Common prompts
- *"Does this project use ML-Agents?"*
- *"List ML-Agents training configs and ONNX models"*
- *"Which scripts reference BehaviorParameters?"*
---
name: unity-model-agnostic-inference
description: >-
Use for on-device or server inference that is not tied to one vendor model:
ONNX Runtime, Sentis, Barracuda legacy, custom .onnx/.tflite/.pb assets,
and swapping models without rewriting gameplay code.
---
# Model-agnostic inference
Vendor models change fast (OpenAI, Gemini, Claude, local GGUF, etc.). For **in-Unity inference**, keep the **runtime + tensor I/O** stable and treat the **weights file** as a swappable asset.
## Discovery (any backend)
1. `get_ai_stack_summary` — packages + model files + script labels
2. `list_ml_model_assets` — `.onnx`, `.nn`, `.tflite`, `.pb`, `.pt`
3. `get_ai_ml_package_inventory` — Sentis / Barracuda / ML-Agents / Muse
4. `find_ai_related_scripts` — labels for Sentis, Barracuda, ONNX, LLM strings
5. `list_ai_prompt_or_config_assets` — prompts/configs (no secrets)
## Architecture checklist (model-agnostic)
- **Interface**: `IInferencer` / `Run(input) -> output` — hide Sentis vs ORT vs HTTP LLM behind one API.
- **Asset**: store only model **files + metadata** (input names, shapes, labels JSON); not hard-coded vendor SDKs in gameplay.
- **Pre/post**: normalize tensors once; keep tokenization/image resize outside the model wrapper.
- **Swap**: change Addressables model key or ScriptableObject reference — no scene rewrite.
- **Cloud LLMs**: never call vendors from the client with long-lived keys; proxy via your backend (see `unity-llm-integration`).
## When to use which skill
| Need | Skill |
|------|--------|
| Inventory everything AI | `unity-ai-audit` |
| Unity Sentis specifically | `unity-sentis-inference` |
| ML-Agents RL | `unity-ml-agents` |
| Chat/RAG / HTTP LLMs | `unity-llm-integration` |
| This skill | Swappable local models + clean boundaries |
## Prompts
- *"List all ML model files and which packages can run them"*
- *"How do we keep inference model-agnostic in this project?"*
---
name: unity-navmesh-npc
description: >-
Use for NavMesh, AI Navigation package, NavMeshAgent, NPC pathfinding,
off-mesh links, or gameplay AI movement (non-ML).
---
# Unity NavMesh & NPC AI
## Discovery
1. `get_navigation_settings` — agent radius, height, areas from ProjectSettings
2. `get_ai_ml_package_inventory` — `com.unity.ai.navigation` vs legacy built-in
3. `find_ai_related_scripts` — label **NavMesh / AI navigation**
4. `list_prefabs_with_component` with `NavMeshAgent`
5. `get_scene_components_by_type` with `NavMeshAgent` per scene
## Implementation checklist
- Bake NavMesh for each playable scene; store `NavMeshData` assets in version control.
- Tune agent avoidance priority and obstacle carving for crowds.
- Off-mesh links for jumps/doors — grep scenes for `OffMeshLink` usage via script scan.
- Separate **locomotion** (NavMeshAgent) from **decision** (state machine / behavior tree).
## Related tools
- `get_tags_and_layers` — AI layers vs player/environment collision
- `get_layer_collision_matrix` — agent vs projectile layers
---
name: unity-sentis-inference
description: >-
Use when integrating ONNX/Sentis/Unity Inference in Unity: model assets,
IWorker, runtime performance, or com.unity.sentis packages.
---
# Unity Sentis / ONNX inference
## Discovery
1. `list_ml_model_assets` — `.onnx`, `.nn`, `.pt` under Assets
2. `get_ai_ml_package_inventory` — Sentis, Barracuda, or `com.unity.ai.inference`
3. `find_ai_related_scripts` — labels **Sentis / InferenceEngine** or **ONNX**
## Implementation checklist
- Import ONNX to `ModelAsset`; use `WorkerFactory` / `IWorker` pattern for Sentis 2.x.
- Match input tensor names/shapes to training export metadata.
- Test on target platform (GPU vs CPU backend); mobile often needs quantized models.
- Barracuda is legacy — prefer Sentis for new work unless project is locked to Barracuda.
## Tools for impact analysis
- `find_references` on model asset path — who loads this ONNX?
- `get_prefab_dependencies` — prefabs pulling large models into builds
- `get_build_size_estimate` — ONNX size in player build
---
name: unity-animation-controller-audit
description: >-
Use for Animator Controllers: states, transitions, clips, avatar masks,
override controllers, and Timeline playables.
---
# Animation controller audit
## Discovery
1. `list_animator_controllers` / `list_animation_clips`
2. `get_animator_states` / `get_animator_transitions`
3. `list_avatar_masks` / `list_animator_override_controllers`
4. `list_timeline_playables`
5. `list_prefabs_with_component` — `Animator`
## Checklist
- Avoid any-state spam; prefer explicit transitions.
- Blend trees: document parameters; cap motion set size.
- Override controllers for character skins, not full duplicates.
- Timeline: keep playables referenced by Addressables if optional cinematics.
## Prompts
- *"List states and transitions in Player.controller"*
- *"Which prefabs have an Animator?"*
---
name: unity-cinemachine
description: >-
Use to audit Cinemachine / camera rigs: package presence, virtual cameras,
and prefab usage (Unity 6 productivity / storytelling stack).
---
# Cinemachine
## Discovery
1. `list_cinemachine_assets` — package + vcam assets/prefabs
2. `list_timeline_playables` — cinematics often drive CM via Timeline
3. `get_feature_set_inference` — Gameplay & Storytelling feature set
4. `find_scripts_by_content` — `CinemachineBrain`, `CinemachineCamera`
## Checklist
- One Brain per main camera; avoid fighting multiple brains.
- Prefer CM 3.x component names if on Unity 6 packages; note legacy VirtualCamera assets.
- Timeline + Impulse + extensions should be version-aligned with the CM package.
- Mobile: watch post-process volume cost on stacked cameras.
## Related
- `unity-animation-controller-audit`, `unity-urp-hdrp-render-audit`
---
name: unity-addressables-shipping
description: >-
Use for Addressables groups, remote vs local content, localization tables,
and shipping catalogs without bloating the player.
---
# Addressables & localization shipping
## Discovery
1. `get_addressables_info` — groups + settings asset path
2. `get_localization_tables` — table assets/CSV/JSON
3. `get_build_size_estimate` / `list_large_assets` — what must stay local
4. `list_packages` — confirm `com.unity.addressables` / localization packages
## Checklist
- Label remote groups clearly; keep boot/critical path local.
- Version catalogs with the player; avoid stale remote hash mismatches.
- Localization: one table set per locale; verify build includes active locales.
- Test offline vs first-download paths.
## Related
- `get_release_readiness`, `get_package_dependency_graph`
---
name: unity-broken-refs-triage
description: >-
Use when prefabs/scenes show Missing Script or pink materials: triage broken
script and asset GUID references and decide fix order.
---
# Broken references triage
## Discovery
1. `get_broken_script_refs` — Missing Script on prefabs/scenes
2. `get_broken_asset_refs` — any missing GUID refs (mats, controllers, …)
3. `find_references` — who still points at a deleted asset GUID/path
4. `get_meta_for_asset` — confirm GUID still exists for survivors
## Fix order
1. Restore or reassign scripts (GUID in `.meta` must match YAML `m_Script`).
2. Fix materials/shaders (`list_materials_using_shader`).
3. Fix Animator/Timeline refs (`list_animator_controllers`, `list_timeline_playables`).
4. Re-run both broken-ref tools until empty.
## Prompts
- *"Which prefabs have missing scripts?"*
- *"List broken asset GUID references"*
---
name: unity-localization
description: >-
Use when auditing Unity Localization: Locale assets, string tables, and
package setup for multi-language shipping.
---
# Localization
## Discovery
1. `get_localization_stack` — package + Locale / StringTable assets
2. `get_localization_tables` — files under Assets/Localization
3. `list_tmp_fonts` — per-locale font assets / fallbacks
4. `get_addressables_info` — localized content often remote
## Checklist
- Default locale + fallbacks must exist offline.
- Smart strings / variables need QA for each grammar-heavy language.
- Font atlases and TMP fallbacks must cover shipping locales.
- Keep table assets Addressable if large; avoid embedding all languages in first download when possible.
## Related
- `unity-addressables-shipping`, `unity-ui-toolkit-tmp`, `unity-build-size-optimization`
---
name: unity-audio-mixer-fmod-wwise
description: >-
Use for Unity audio clips/mixers and middleware: FMOD banks and Wwise
soundbanks / project paths.
---
# Audio, FMOD & Wwise
## Discovery
1. `list_audio_clips` / `list_audio_mixers`
2. `get_audio_settings` — DSP buffer, global volume
3. `get_fmod_config` — banks path + bank files
4. `get_wwise_config` — soundbanks / `.wproj`
5. `list_large_assets` — uncompressed WAV often dominate size
## Checklist
- Load type: streaming for music, decompress on load sparingly.
- Mixer groups/snapshots for ducking and pause menus.
- Middleware: keep bank build step in CI; don't commit huge intermediate caches.
- Platform sample rates/compression consistent with quality tiers.
## Related
- `unity-build-size-optimization`
---
name: unity-assembly-architecture
description: >-
Use for asmdef architecture: assembly graph, cycles, execution order,
scripting defines, and which assembly owns a script path.
---
# Assembly architecture
## Discovery
1. `list_assemblies` / `get_assembly_dependency_graph`
2. `detect_assembly_cycles`
3. `get_assembly_for_path` / `list_scripts_by_assembly`
4. `list_asmdef_references` — reverse dependents
5. `get_script_execution_order` / `get_scripting_defines`
## Checklist
- Runtime vs Editor assemblies split cleanly.
- No cycles; prefer facades over god assemblies.
- Defines documented per platform (`get_scripting_defines`).
- Tests in dedicated asmdefs (`list_test_assemblies`).
## Prompts
- *"Do we have assembly cycles?"*
- *"Which assembly contains Assets/Scripts/Player.cs?"*
---
name: unity-ci-github-actions
description: >-
Use to inspect Unity CI: GitHub Actions workflows, Jenkins, Unity Cloud Build,
Git LFS, and Plastic SCM project config.
---
# Unity CI & version control
## Discovery
1. `list_ci_configs` — workflows / Jenkinsfile / cloud build JSON
2. `get_git_lfs_tracked` — LFS patterns from `.gitattributes`
3. `get_plastic_config` — Plastic workspace if used
4. `get_version_control_settings` — Force Text / meta visibility
5. `get_release_readiness` — gate merges on health
## Checklist
- Track `.meta` files; match `Visible Meta Files`.
- LFS for large binaries (textures, audio, models).
- Cache Library carefully; never commit Library/.
- License activation secrets stay in CI vaults, not the repo.
## Prompts
- *"What CI workflows exist?"*
- *"Which paths are Git LFS?"*
---
name: unity-input-system-setup
description: >-
Use for Unity Input: legacy InputManager axes and New Input System
.inputactions action maps, bindings, and player controls.
---
# Input System setup
## Discovery
1. `get_input_axes` — legacy axes in InputManager
2. `list_input_action_assets` — `.inputed` assets
3. `get_input_actions_summary` — maps/actions for one asset
4. `list_packages` — `com.unity.inputsystem` present?
5. `find_scripts_by_content` — `PlayerInput`, `InputAction`
## Checklist
- Prefer one paradigm (new Input System) for new projects.
- Document Active Input Handling in Player Settings (`get_player_settings`).
- Keep UI Toolkit / EventSystem input modules consistent with that choice.
- Rebindable actions: store overrides outside StreamingAssets carefully.
## Related
- `get_tags_and_layers`, `list_ui_documents`
---
name: unity-adaptive-performance
description: >-
Use for Adaptive Performance and Project Auditor: thermal/bottleneck scalers
and static project health (Unity 6.4–6.5 optimization story).
---
# Adaptive Performance & Project Auditor
## Discovery
1. `get_optimization_package_inventory` — AP + Auditor packages/assets/scripts
2. `get_quality_settings` / `get_graphics_settings` — scaler targets
3. `unity-runtime-performance` skill — static FPS/GPU proxies
4. `get_platform_build_hints` — mobile/console thermal-relevant platforms
## Checklist
- Adaptive Performance needs a **provider** (device/simulator/Apple/Basic) for the target platform.
- Custom scalers should live in scaler profiles — verify assets exist under Assets.
- Project Auditor (Editor-integrated in 6.4+) finds obsolete APIs between Unity versions — run before upgrades.
- Pair with Addressables/build-size skills; AP does not fix oversized downloads.
## Related
- `unity-runtime-performance`, `unity-memory-asset-pressure`, `unity-build-size-optimization`
---
name: unity-build-size-optimization
description: >-
Use to shrink player/install size: estimate build weight, find large assets,
textures, Addressables, and unused or oversized content.
---
# Unity build size optimization
## Discovery
1. `get_build_size_estimate` — weight implied by build scenes
2. `list_large_assets` — files over N MB (default 5)
3. `get_texture_meta` — maxSize / compression hints per texture
4. `get_addressables_info` — whether content is remote vs local
5. `list_ml_model_assets` — ONNX/weights often dominate size
## Checklist
- Cap texture `maxTextureSize`; prefer ASTC/ETC2 on mobile.
- Move DLC/optional content to Addressables remote groups.
- Drop unused scenes from `EditorBuildSettings`.
- Compress audio; streaming for long music.
- Keep ML models out of the main Data folder when possible.
## Related
- `list_build_scenes`, `get_scene_referenced_assets`, `list_video_clips`
---
name: unity-memory-asset-pressure
description: >-
Use to find memory/disk pressure from assets: large files, textures, audio,
video, ML models, and Addressables that should be remote.
---
# Memory & asset pressure
## Discovery
1. `list_large_assets` (try 2MB and 5MB thresholds)
2. `get_build_size_estimate` — what build scenes pull in
3. `get_texture_meta` on top offenders
4. `list_audio_clips` / `list_video_clips` / `list_ml_model_assets`
5. `get_addressables_info` — candidates to move remote
## Checklist
- Textures: lower maxSize; crunch/ASTC; no uncompressed UI atlases on mobile.
- Audio: Vorbis/ADPCM; load type streaming for long clips.
- Video: keep out of first scene; stream or Addressables.
- ML models: don't embed large ONNX in boot scene.
- Duplicate fonts/TMP assets (`list_tmp_fonts`, `list_legacy_font_assets`).
## Related
- `unity-runtime-performance`, `unity-build-size-optimization`, `unity-addressables-shipping`
---
name: unity-runtime-performance
description: >-
Use for runtime performance triage from the project filesystem: quality tiers,
graphics/URP-HDRP settings, lighting, large assets, scene complexity proxies,
and build-size hotspots (no Editor Profiler required).
---
# Runtime performance (filesystem proxies)
This MCP **cannot** attach to the Unity Profiler. Use these tools as a **static triage** before opening Profiler/Frame Debugger.
## Discovery order
| Step | Tool | What it tells you |
|------|------|-------------------|
| 1 | `get_quality_settings` | Tier count, shadows, AA, soft particles |
| 2 | `get_graphics_settings` / `list_render_pipelines` | Pipeline + SRP batcher-related assets |
| 3 | `get_lighting_scene_info` / `list_lighting_settings_assets` | GI / lighting mode cost |
| 4 | `get_scene_summary` + `get_all_components_by_type` | Cameras, lights, particle proxies per scene |
| 5 | `list_large_assets` / `get_build_size_estimate` | Memory/disk pressure candidates |
| 6 | `list_lod`-N/A — use `search_assets_by_name` `*LOD*` / meshes | LOD presence heuristic |
| 7 | `get_time_settings` | Fixed timestep vs render cost |
## Checklist
- **One main camera** strategy per scene; disable unused cameras.
- Cap realtime lights; prefer baked/mixed where possible.
- URP/HDRP: match quality tier to device; strip unused features.
- Textures: maxSize + compression (`get_texture_meta`).
- Audio: streaming for music (`unity-audio-mixer-fmod-wwise`).
- UI: avoid full-screen overdraw; atlas sprites (`unity-2d-tilemap-sprites`).
## Follow-ups
- Size-focused: `unity-build-size-optimization`
- Memory assets: `unity-memory-asset-pressure`
- Render stack: `unity-urp-hdrp-render-audit`
## Report template
```markdown
## Performance triage (static)
- Quality tiers: ...
- Pipeline: URP/HDRP/...
- Build scenes: N; heavy components: ...
- Largest assets: ...
- Suspected issues: lights / textures / audio / UI
- Next: open Profiler on <scene>
```
---
name: unity-dots-subscenes
description: >-
Use for DOTS/ECS packaging: .subscene assets, related packages, and how
content splits from classic scenes.
---
# DOTS / subscenes
## Discovery
1. `list_subscenes` — `.subscene` assets under Assets
2. `get_feature_set_inference` / `list_packages` — Entities, Netcode, Baking
3. `list_build_scenes` — which classic scenes host subscenes
4. `find_scripts_by_content` — `ISystem`, `IComponentData`, `SubScene`
## Checklist
- Bake in CI or editor; commit/bake artifacts per team policy.
- Keep conversion/baking assemblies Editor-only where appropriate.
- Netcode: ghost prefabs vs subscene static content boundaries clear.
## Related
- `unity-assembly-architecture`, `get_assembly_dependency_graph`
---
name: unity-platform-player-settings
description: >-
Use for a Unity 6 platform pass: Android/WebGL/iOS PlayerSettings snippets,
scripting backends, and platform packages (foldables, Web threads, etc.).
---
# Platform / Player Settings
## Discovery
1. `get_platform_build_hints` — Android/WebGL snippets, build target, hints
2. `get_player_settings` / `get_build_target_info` — identity + active target
3. `list_build_profiles` — per-platform profiles
4. `get_xr_settings` — if XR/mobile XR relevant
## Checklist
- Confirm **min SDK / Web memory / threads** against the Unity version’s platform notes.
- Unity 6.4+ Web can enable Burst multithreading — only if `webGLThreadsSupport` and job safety allow it.
- Android 6.5 notes (foldables, LTO, edge-to-edge) need matching Player/Gradle settings in Editor.
- Dedicated server / headless: separate build profile and stripped packages.
## Related
- `unity-build-profiles`, `unity-xr-vr-audit`, `unity-release-readiness`
---
name: unity-xr-vr-audit
description: >-
Use to audit XR/VR setup: XR settings assets, related packages, scenes, and
input/action assets for headsets.
---
# XR / VR audit
## Discovery
1. `get_xr_settings` — XR project settings assets
2. `list_packages` / `get_feature_set_inference` — XR Interaction, OpenXR, provider packages
3. `list_input_action_assets` + `get_input_actions_summary` — XR controllers
4. `list_build_scenes` — XR rigs in shipping scenes
5. `list_prefabs_with_component` — common XR origin / interactor names via script search
## Checklist
- Single XR loader stack per platform (OpenXR preferred when possible).
- Performance: target frame time, foveation, pass-through costs.
- Interaction: teleport vs continuous locomotion documented.
## Related
- `unity-input-system-setup`, `unity-qa-scene-coverage`
---
name: unity-prefab-architecture
description: >-
Use to understand prefab structure: variants, dependencies, components,
scripts, and impact analysis before refactors.
---
# Prefab architecture
## Discovery
1. `list_prefabs` / `list_prefab_variants`
2. `get_prefab_summary` — root name, component types
3. `get_prefab_script_guids` + `get_script_public_api`
4. `get_prefab_dependencies` — outbound asset refs
5. `list_prefabs_with_component` — e.g. Animator, Rigidbody, NetworkIdentity
## Checklist
- Prefer variants over copy-paste prefab trees.
- Keep nested prefabs shallow; document override intent.
- Before deleting an asset, `find_references` + `get_prefab_dependencies`.
- Missing scripts → `unity-broken-refs-triage` skill.
## Prompts
- *"Summarize Player.prefab and its dependencies"*
- *"Which prefabs use Animator?"*
---
name: unity-accessibility
description: >-
Use when auditing Unity Accessibility: screen readers, AccessibilityNode,
AssistiveSupport (desktop Narrator/VoiceOver support from Unity 6.3+).
---
# Accessibility
## Discovery
1. `get_accessibility_stack` — module/package + API script hits
2. `list_ui_documents` — UI Toolkit screens that need accessible labels
3. `find_scripts_by_content` — `AccessibilityNode`, `AssistiveSupport`
## Checklist
- Mobile (TalkBack / VoiceOver) and desktop (Narrator / VoiceOver) paths both matter on Unity 6.3+.
- Every interactive control should expose role + label; test with screen reader on.
- Keep hierarchy order matching visual reading order for focus traversal.
- Do not rely on color alone for critical state.
## Related
- `unity-ui-toolkit-tmp`, `unity-qa-scene-coverage`
---
name: unity-build-profiles
description: >-
Use for Unity 6 Build Profiles: per-profile scenes, scripting defines, and
Player/Quality overrides instead of a single classic build settings list.
---
# Build Profiles (Unity 6+)
## Discovery
1. `list_build_profiles` — BuildProfile assets under Assets / ProjectSettings
2. `list_build_scenes` — classic EditorBuildSettings (fallback / shared list)
3. `get_scripting_defines` — global + asmdef defines
4. `get_platform_build_hints` — platform PlayerSettings snippets
## Checklist
- Each shippable configuration (dev, staging, store, dedicated server) should be a **named Build Profile**.
- Prefer profile-scoped scene lists and scripting defines over editing one global list by hand.
- Document which profile CI uses (`-activeBuildProfile` / batchmode args).
- If `list_build_profiles` is empty, the project may still be on classic build settings — migrate deliberately.
## Related
- `unity-release-readiness`, `unity-ci-github-actions`, `unity-platform-player-settings`
---
name: unity-physics-audit
description: >-
Use to audit Unity physics: classic 3D/2D modules, Unity Physics (ECS), and
Physics Core 2D (Unity 6.3–6.5).
---
# Physics audit
## Discovery
1. `get_physics_stack` — packages, module hints, script labels
2. `get_physics_settings` — DynamicsManager / Physics2D settings
3. `get_layer_collision_matrix` — layer collision matrix
4. `list_packages` — `com.unity.physics`, Havok, U2D Physics
## Checklist
- Know which backend ships: classic PhysX / Box2D vs Unity Physics vs Physics Core 2D.
- Unity 6.4+ allows **disabling** built-in physics modules to shrink builds — only if unused.
- Physics Core 2D was renamed from LowLevelPhysics2D (`Unity.U2D.Physics`) in 6.5.
- For ECS projects, confirm baking/authoring components and no accidental dual simulation.
## Related
- `unity-dots-subscenes`, `unity-2d-tilemap-sprites`, `unity-build-size-optimization`
---
name: unity-release-readiness
description: >-
Use before shipping a Unity build: version, build scenes, broken refs,
assembly cycles, large assets, changelog, and package health.
---
# Unity release readiness
## One-shot
1. `get_release_readiness`
2. `get_project_version` + `get_changelog`
3. `list_build_scenes`
## Deep dive
| Step | Tool | Why |
|------|------|-----|
| 1 | `get_broken_script_refs` / `get_broken_asset_refs` | Missing scripts/GUIDs block QA |
| 2 | `detect_assembly_cycles` | Compile-time landmines |
| 3 | `list_large_assets` | Unexpected install/download size |
| 4 | `get_package_dependency_graph` | Accidental heavy packages |
| 5 | `list_ci_configs` | Confirm release pipeline exists |
## Report template
```markdown
## Release readiness
- Version: ...
- Build scenes: N
- Broken script refs: N
- Broken asset refs: N
- Assembly cycles: yes/no
- Largest assets: ...
- Blockers: ...
```
# Bundled skills
Workflow guides for **unity-mcp-server**, organized like the Unity Editor / Project window.
Discover with `list_ai_skills` / `read_ai_skill` (skill **id** = leaf folder name).
## Layout
```text
skills/
project/ Project Settings, packages, ship gate, physics, a11y, build profiles
scenes/ Build scenes, hierarchy, lighting, QA
prefabs/ Prefab structure & variants
assets/ References, Addressables, localization, missing GUIDs
code/ Assemblies, Input System, CI
rendering/ URP / HDRP / BIRP migration
animation/ Animator / Timeline / Cinemachine
2d/ Sprites / tilemaps
ui/ UI Toolkit / TMP
audio/ Mixer / FMOD / Wwise
ai-ml/ AI, ML, NavMesh, LLM, model-agnostic
performance/ Runtime, build size, memory, Adaptive Performance
services/ PlayFab, Firebase, Steam, ads, multiplayer, UGS
platform/ DOTS, XR, PlayerSettings / platforms
README.md
```
## By category
### project
| Id | Focus |
|----|--------|
| `unity-release-readiness` | Ship gate |
| `unity-build-profiles` | Unity 6 Build Profiles |
| `unity-physics-audit` | Classic + Unity Physics + Physics Core 2D |
| `unity-accessibility` | Screen readers / Accessibility APIs |
### scenes
| Id | Focus |
|----|--------|
| `unity-scenes-workflow` | Build list → summary → hierarchy → components |
| `unity-qa-scene-coverage` | QA pass over build / all scenes |
### prefabs
| Id | Focus |
|----|--------|
| `unity-prefab-architecture` | Prefabs / variants / script GUIDs |
### assets
| Id | Focus |
|----|--------|
| `unity-broken-refs-triage` | Missing scripts & GUIDs |
| `unity-addressables-shipping` | Local / remote content |
| `unity-localization` | Locales / string tables |
### code
| Id | Focus |
|----|--------|
| `unity-assembly-architecture` | asmdefs / cycles |
| `unity-input-system-setup` | Input System |
| `unity-ci-github-actions` | CI / LFS / Plastic |
### rendering · animation · 2d · ui · audio
| Id | Focus |
|----|--------|
| `unity-urp-hdrp-render-audit` | SRP / shaders / lighting |
| `unity-birp-urp-migration` | Built-in RP deprecation → URP/HDRP |
| `unity-animation-controller-audit` | Animator / Timeline |
| `unity-cinemachine` | Cinemachine cameras |
| `unity-2d-tilemap-sprites` | 2D art |
| `unity-ui-toolkit-tmp` | UI Toolkit / TMP |
| `unity-audio-mixer-fmod-wwise` | Audio middleware |
### ai-ml
| Id | Focus |
|----|--------|
| `unity-ai-audit` | Full AI/ML inventory |
| `unity-model-agnostic-inference` | Swap models without vendor lock-in |
| `unity-ml-agents` | ML-Agents RL |
| `unity-sentis-inference` | Unity Sentis |
| `unity-navmesh-npc` | NavMesh / NPC AI |
| `unity-llm-integration` | Chat/RAG / HTTP LLMs |
### performance
| Id | Focus |
|----|--------|
| `unity-runtime-performance` | Static FPS/GPU triage proxies |
| `unity-build-size-optimization` | Install / download size |
| `unity-memory-asset-pressure` | Large textures/audio/models |
| `unity-adaptive-performance` | Adaptive Performance + Project Auditor |
### services
| Id | Focus |
|----|--------|
| `unity-multiplayer-netcode` | NGO / Relay / Lobby / third-party netcode |
| `unity-ugs-cloud` | Unity Gaming Services / Cloud Code |
| `unity-playfab-backend` | PlayFab |
| `unity-firebase-mobile` | Firebase |
| `unity-steam-discord-presence` | Steam / Discord |
| `unity-ads-analytics-crash` | Ads + crash SDKs |
### platform
| Id | Focus |
|----|--------|
| `unity-dots-subscenes` | DOTS / ECS |
| `unity-xr-vr-audit` | XR / VR |
| `unity-platform-player-settings` | Android / WebGL / platform PlayerSettings |
## Notes
- Skills are **markdown only**; tools enforce project-root FS confinement.
- Runtime Profiler attachment is out of scope — use `unity-runtime-performance` then open Unity Profiler.
---
name: unity-birp-urp-migration
description: >-
Use when assessing Built-in Render Pipeline vs URP/HDRP. BIRP is deprecated
in Unity 6.5 and will be obsolete after 6.7 LTS.
---
# BIRP → URP/HDRP migration
## Discovery
1. `get_render_pipeline_migration_hints` — likely pipeline + deprecation hints
2. `list_render_pipelines` — URP/HDRP assets and volumes
3. `list_materials` / `list_shaders` / `list_shader_graphs` — conversion surface area
4. `unity-urp-hdrp-render-audit` skill — deep SRP inventory
## Checklist
- If likely **birp**, plan Render Pipeline Converter (Editor) and material/shader conversion time.
- URP Compatibility Mode was **removed** in Unity 6.4 — custom passes need Render Graph.
- Prefer one pipeline per product; dual URP+HDRP packages are a red flag unless intentional.
- Re-bake lighting and verify Shader Graph targets after conversion.
## Related
- `unity-urp-hdrp-render-audit`, `unity-runtime-performance`
---
name: unity-urp-hdrp-render-audit
description: >-
Use to audit URP/HDRP: pipeline assets, volume profiles, shaders, Shader Graphs,
VFX Graphs, and lighting settings assets.
---
# URP / HDRP render audit
## Discovery
1. `list_render_pipelines` — pipeline + volume profile assets
2. `list_shaders` / `list_shader_graphs` / `list_vfx_graphs`
3. `list_materials` / `list_materials_using_shader`
4. `list_lighting_settings_assets` / `get_lighting_scene_info`
5. `get_graphics_settings` / `get_quality_settings`
## Checklist
- One active pipeline asset per platform quality tier.
- Strip unused shader variants; watch mobile shader cost.
- Volume profiles: global vs local; avoid stacking conflicts.
- Confirm lighting mode matches GI workflow in build scenes.
## Related
- `get_feature_set_inference`, `list_large_assets`
---
name: unity-qa-scene-coverage
description: >-
Use for QA scene coverage: build scenes, tags, components, prefabs, test
assemblies, and release blockers.
---
# QA scene coverage
## Discovery
1. `list_build_scenes` vs `list_all_scenes` — what's shipping
2. `get_scene_summary` / `get_scene_hierarchy_flat` per build scene
3. `get_scene_objects_by_tag` — Spawn, Player, Checkpoint, …
4. `get_all_components_by_type` — cameras, lights, audio listeners
5. `list_test_assemblies` + `get_release_readiness`
## Checklist
- Every build scene has exactly one AudioListener / main Camera strategy.
- Critical tags exist (`get_tags_and_layers`).
- Smoke: missing scripts zero (`get_broken_script_refs`).
- Input/actions covered (`list_input_action_assets`).
## Prompts
- *"Which scenes are in the build and what's tagged Spawn?"*
- *"List test assemblies and release readiness"*
---
name: unity-scenes-workflow
description: >-
Use for Unity scene exploration the way developers work in the Editor:
build list, all scenes, hierarchy, components, lighting, then broken refs.
---
# Unity scenes workflow
Unity developers live in **Scenes**. Use this order — same mental model as File → Build Settings and the Hierarchy.
## 1. What ships
| Step | Tool |
|------|------|
| Build order | `list_build_scenes` |
| Project snapshot | `get_project_info` |
## 2. Inventory
| Step | Tool |
|------|------|
| Every `.unity` under Assets | `list_all_scenes` |
| ECS/DOTS | `list_subscenes` |
## 3. Open a scene (static)
Pick a path from the lists above (e.g. `Assets/Scenes/Main.unity`).
| Step | Tool | Notes |
|------|------|--------|
| Root objects + component density | `get_scene_summary` | Fast overview |
| Flat hierarchy | `get_scene_hierarchy_flat` | Names + layers |
| Find Cameras / Lights / etc. | `get_scene_components_by_type` | Pass type name |
| By tag | `get_scene_objects_by_tag` | e.g. Spawn |
| Lighting / GI | `get_lighting_scene_info` | |
## 4. Cross-scene checks
| Step | Tool |
|------|------|
| All Cameras/Lights project-wide | `get_all_components_by_type` |
| Missing GUIDs in scenes | `get_broken_asset_refs` |
| QA pass over build scenes | Read skill `unity-qa-scene-coverage` |
## 5. Related domains
- Prefabs used in scenes → `unity-prefab-architecture`
- Addressables / remote content → `unity-addressables-shipping`
- Runtime cost proxies → `unity-runtime-performance`
## Out of scope
No live Hierarchy edit, Play Mode, or Scene view — filesystem YAML only.
---
name: unity-ads-analytics-crash
description: >-
Use to inventory ads and crash/analytics SDKs: Unity Ads, AdMob, ironSource,
Sentry, Crashlytics, BugSnag, Unity Analytics.
---
# Ads, analytics & crash reporting
## Discovery
1. `get_ads_config` — Unity Ads / AdMob / ironSource package presence
2. `get_analytics_or_crash_config` — Sentry, Crashlytics, BugSnag, Analytics
3. `list_packages` — confirm versions
4. `find_scripts_by_content` — `Advertisement`, `SentrySdk`, `Firebase.Crashlytics`
## Checklist
- One crash reporter primary; avoid duplicate symbolication noise.
- Ad consent / ATT flows documented for mobile stores.
- DSN/API keys via secure config, not hardcoded in scripts.
## Related
- `unity-firebase-mobile`, `get_cloud_services_config`
---
name: unity-firebase-mobile
description: >-
Use for Firebase on Unity mobile: google-services.json, GoogleService-Info.plist,
project ID discovery (paths only; no secret dumping).
---
# Firebase mobile
## Discovery
1. `get_firebase_config` — config file paths + project ID
2. `list_packages` — Firebase Unity SDKs
3. `get_analytics_or_crash_config` — Crashlytics overlap
4. `list_ci_configs` — whether configs are injected in CI
## Checklist
- Keep plist/json out of public forks when possible; use CI secrets + templating.
- Separate Firebase projects per environment.
- Confirm iOS/Android bundle IDs match `get_player_settings`.
## Security note
This server returns **paths and project IDs**, not private keys. Still treat config files as sensitive in VCS policy.
---
name: unity-multiplayer-netcode
description: >-
Use to audit Unity multiplayer: Netcode for GameObjects, Transport, Relay/Lobby,
or third-party stacks (Mirror, FishNet, Photon).
---
# Multiplayer / Netcode
Unity 6 treats multiplayer as a first-class pillar. Start with discovery, then architecture.
## Discovery
1. `get_multiplayer_stack` — packages + NetworkBehaviour / Relay / Lobby / Photon hits
2. `list_packages` — confirm `com.unity.netcode.gameobjects`, transport, multiplayer tools
3. `get_ugs_cloud_stack` — Relay/Lobby often sit beside UGS Auth
4. `list_build_profiles` / `list_build_scenes` — dedicated server vs client scenes
## Checklist
- One authority model (server/host/client); document who owns player state.
- NetworkVariables / RPCs only for replicated state — keep large assets on Addressables.
- Separate **client** and **server** (or host) build profiles with scripting defines.
- Never commit Relay/allocation secrets; use environment configs.
- If no NGO packages but custom sockets exist, note that in the audit report.
## Related
- `unity-ugs-cloud`, `unity-build-profiles`, `unity-platform-player-settings`
---
name: unity-playfab-backend
description: >-
Use when integrating or auditing PlayFab: title ID, config assets, and
related package presence.
---
# PlayFab backend
## Discovery
1. `get_playfab_config` — title ID + config paths
2. `list_packages` — PlayFab / Azure packages
3. `find_scripts_by_content` — `PlayFabClientAPI`, `PlayFabSettings`
4. `get_analytics_or_crash_config` — overlapping telemetry
## Checklist
- Title ID is environment-specific (dev/stage/prod); never commit secret keys.
- Prefer economy/catalog data from server; keep client authoritative checks minimal.
- Align player identity with Steam/mobile SDKs if multi-platform.
## Related
- `unity-steam-discord-presence`, `unity-ads-analytics-crash`
---
name: unity-steam-discord-presence
description: >-
Use for Steamworks and Discord SDK presence in a Unity project: app ID file,
plugin folders, and related scripts.
---
# Steam & Discord presence
## Discovery
1. `get_steam_config` — `steam_appid.txt` + Steamworks plugin path
2. `get_discord_config` — Discord SDK under Plugins
3. `find_scripts_by_content` — `Steamworks`, `Discord`
4. `get_player_settings` — product/company naming consistency
## Checklist
- `steam_appid.txt` is for local/dev; shipping uses steam_api correctly.
- Don't commit partner secrets; keep depot scripts outside the game repo if needed.
- Discord social SDK: match SDK binary to CPU architecture per platform.
## Related
- `unity-playfab-backend`, `unity-ci-github-actions`
---
name: unity-ugs-cloud
description: >-
Use when auditing Unity Gaming Services: Authentication, Cloud Code, Economy,
Cloud Save, Remote Config, and Unity Connect project linkage.
---
# Unity Gaming Services / Cloud Code
## Discovery
1. `get_ugs_cloud_stack` — `com.unity.services.*` packages + script hits
2. `get_cloud_services_config` — Unity Connect / dashboard linkage
3. `get_multiplayer_stack` — Relay / Lobby / Vivox often co-installed
4. `get_analytics_or_crash_config` — avoid double-instrumentation with third-party SDKs
## Checklist
- Initialize `UnityServices` once; gate gameplay until Auth completes when required.
- Cloud Code is authoritative for economy mutations — do not trust client balances.
- Environments (dev/prod) must not share the same secrets in source control.
- Prefer Remote Config for tunables; keep defaults offline-safe.
## Related
- `unity-multiplayer-netcode`, `unity-playfab-backend`, `unity-firebase-mobile`
---
name: unity-ui-toolkit-tmp
description: >-
Use for UI Toolkit (UXML/USS) and TextMeshPro: documents, fonts, TMP settings,
and UI-related assets.
---
# UI Toolkit & TextMeshPro
## Discovery
1. `list_ui_documents` — `.uxml` / `.uss`
2. `list_tmp_fonts` / `get_tmp_settings_path`
3. `list_legacy_font_assets` — migration leftovers
4. `find_scripts_by_content` — `UIDocument`, `TextMeshPro`
5. `get_input_actions_summary` — UI action maps if using Input System
## Checklist
- Prefer UI Toolkit for editor-like / runtime panels; TMP for rich text in world/canvas hybrids as needed.
- One TMP Settings asset; shared font assets to cut duplicates.
- Theme via USS variables; avoid one-off inline styles at scale.
- Remove legacy `.ttf` UI fonts once migrated.
## Related
- `unity-input-system-setup`, `list_prefabs_with_component`
+70
-1

@@ -8,2 +8,71 @@ # Changelog

## [1.7.0] - 2026-08-04
### Added
- **Unity 6 coverage skills (10):** `unity-multiplayer-netcode`, `unity-ugs-cloud`, `unity-build-profiles`, `unity-physics-audit`, `unity-accessibility`, `unity-adaptive-performance`, `unity-birp-urp-migration`, `unity-cinemachine`, `unity-localization`, `unity-platform-player-settings`.
- **Matching tools (10):** `get_multiplayer_stack`, `list_build_profiles`, `get_physics_stack`, `get_optimization_package_inventory`, `get_ugs_cloud_stack`, `get_accessibility_stack`, `list_cinemachine_assets`, `get_platform_build_hints`, `get_render_pipeline_migration_hints`, `get_localization_stack`.
- **`unity-scenes-workflow`** skill for build list → hierarchy → components.
### Changed
- Skills reorganized into **Unity Editor domains** (`scenes/`, `prefabs/`, `assets/`, `project/`, `code/`, …). Skill **ids unchanged**.
- Trimmed repo docs: removed `docs/reference/`, per-version `docs/release-notes/`, and duplicate guides. History lives in this changelog.
- **39** bundled skills; **130+** tools. Backward compatible with 1.6.x MCP configs.
---
## [1.6.6] - 2026-08-04
### Added
- **Model-agnostic inference** skill: `unity-model-agnostic-inference`.
- **Performance** skills: `unity-runtime-performance`, `unity-memory-asset-pressure` (with existing build-size skill).
### Changed
- **Skills organized by category:** `ai-ml/`, `shipping/`, `content/`, `performance/`, `integrations/`, `platform/` + `skills/README.md` index.
- Skill loader recursively discovers skills; `list_ai_skills` now includes `category`.
---
## [1.6.5] - 2026-08-04
### Added
- **20 new agent skills** (25 total): release readiness, build size, broken refs, Addressables, prefabs, input, CI, URP/HDRP, animation, 2D, UI/TMP, audio/FMOD/Wwise, QA coverage, PlayFab, Firebase, Steam/Discord, ads/analytics, DOTS, XR, assembly architecture.
### Changed
- **Smaller npm package:** exclude `assets/` (~4.5MB diagram) and `.github/` from the published tarball; README diagram now loads from GitHub raw URL.
---
## [1.6.4] - 2026-08-04
### Security
- **Path confinement:** `readFileSafe` / `listFilesRecursive` and all FS walks now reject `../`, absolute segments, and symlink escapes outside `UNITY_PROJECT_PATH` (`resolveUnderRoot` + `realpath`).
- **ReDoS:** search/pattern tools use escaped literal / wildcard compilers (`literalRegExp` / `wildcardRegExp`) with length caps.
- **Hub disclosure:** `list_unity_hub_projects` redacts absolute paths unless `UNITY_MCP_ALLOW_HUB_PATHS=1`.
- **Size caps:** agent docs, repo docs, changelog, and bundled skills truncated at 200KB.
---
## [1.6.3] - 2026-08-04
### Security
- Reverted Socket Registry (`@socketregistry/*`) overrides from 1.6.2. Those cleared “optimized override available” alerts but introduced **Unpopular package** quality hits (e.g. `@socketregistry/es-define-property`) and lowered Supply Chain / Quality scores. CVE patches from 1.6.1 remain.
---
## [1.6.2] - 2026-08-04
### Security
- Applied Socket Registry overrides for the 8 remaining “Socket optimized override available” supply-chain alerts: `es-define-property`, `function-bind`, `gopd`, `has-symbols`, `hasown`, `object-assign`, `safer-buffer`, `side-channel`.
---
## [1.6.1] - 2026-08-04

@@ -13,3 +82,3 @@

- Cleared Socket.dev Dependency Alerts Report (45 CVE rows, 0.1–0.45): bumped `@modelcontextprotocol/sdk` to `^1.30.0` and pinned patched transitive overrides (`hono@4.13.0`, `@hono/node-server@2.1.0`, `express-rate-limit@8.6.2`, `path-to-regexp@8.4.2`, `body-parser@2.3.0`, `qs@6.15.3`, `fast-uri@4.1.2`, `ip-address@10.4.0`, `ajv@8.20.0`, `esbuild@0.28.1`). See [docs/release-notes/v1.6.1.md](./docs/release-notes/v1.6.1.md).
- Cleared Socket.dev Dependency Alerts Report (45 CVE rows, 0.1–0.45): bumped `@modelcontextprotocol/sdk` to `^1.30.0` and pinned patched transitive overrides (`hono@4.13.0`, `@hono/node-server@2.1.0`, `express-rate-limit@8.6.2`, `path-to-regexp@8.4.2`, `body-parser@2.3.0`, `qs@6.15.3`, `fast-uri@4.1.2`, `ip-address@10.4.0`, `ajv@8.20.0`, `esbuild@0.28.1`). Details under **[1.6.1]** in this changelog.
- `npm audit` reports **0 vulnerabilities**.

@@ -16,0 +85,0 @@ - Runtime path unchanged: this server uses **stdio only**; HTTP/SSE demo code inside the MCP SDK is not loaded.

@@ -10,2 +10,3 @@ #!/usr/bin/env node

import { searchToolsCatalog } from "./tools-catalog.js";
import { truncateText } from "./readers/helpers.js";
function getProjectRoot() {

@@ -25,3 +26,3 @@ const env = process.env.UNITY_PROJECT_PATH;

name: "unity-mcp-server",
version: "1.6.1",
version: "1.7.0",
});

@@ -57,9 +58,9 @@ const text = (s) => ({ content: [{ type: "text", text: s }] });

const include = args?.include_repo_understanding ?? false;
let content = R.readFileSafe(projectRoot, ".agents", "AGENT.md") ?? "(No .agents/AGENT.md found)";
let content = R.readTextCapped(projectRoot, ".agents", "AGENT.md") ?? "(No .agents/AGENT.md found)";
if (include) {
const rep = R.readFileSafe(projectRoot, "REPO_UNDERSTANDING.md");
const rep = R.readTextCapped(projectRoot, "REPO_UNDERSTANDING.md");
if (rep)
content += "\n\n---\n\n# REPO_UNDERSTANDING.md\n\n" + rep;
}
return text(content);
return text(truncateText(content));
});

@@ -284,3 +285,3 @@ // --- 1. Project & package info ---

server.registerTool("list_package_samples", { description: "List Samples folders or sample paths under Packages.", inputSchema: {} }, async () => json(R.listPackageSamples(projectRoot)));
server.registerTool("list_unity_hub_projects", { description: "List Unity projects from Unity Hub (projects-v1.json). Does not require UNITY_PROJECT_PATH.", inputSchema: {} }, async () => json(R.listUnityHubProjects()));
server.registerTool("list_unity_hub_projects", { description: "List Unity Hub projects (basenames only by default; set UNITY_MCP_ALLOW_HUB_PATHS=1 for full paths). Does not require UNITY_PROJECT_PATH.", inputSchema: {} }, async () => json(R.listUnityHubProjects()));
// --- 17. Render pipelines ---

@@ -370,2 +371,43 @@ server.registerTool("list_render_pipelines", { description: "List render pipeline assets and volume profiles (URP/HDRP).", inputSchema: {} }, async () => json(R.listRenderPipelines(projectRoot)));

}, async (args) => json(searchToolsCatalog(args?.query)));
// --- Unity 6.x coverage gaps ---
server.registerTool("get_multiplayer_stack", {
description: "Detect Netcode / multiplayer packages and NetworkBehaviour-style scripts (NGO, Relay, Lobby, Mirror, Photon, …).",
inputSchema: {},
}, async () => json(R.getMultiplayerStack(projectRoot)));
server.registerTool("list_build_profiles", {
description: "List Unity 6 Build Profile assets (plus whether classic EditorBuildSettings exists).",
inputSchema: {},
}, async () => json(R.listBuildProfiles(projectRoot)));
server.registerTool("get_physics_stack", {
description: "Physics packages (Unity Physics, Physics Core 2D), module hints, and Rigidbody/physics script hits.",
inputSchema: {},
}, async () => json(R.getPhysicsStack(projectRoot)));
server.registerTool("get_optimization_package_inventory", {
description: "Adaptive Performance and Project Auditor packages, scaler assets, and script usage.",
inputSchema: {},
}, async () => json(R.getOptimizationPackageInventory(projectRoot)));
server.registerTool("get_ugs_cloud_stack", {
description: "Unity Gaming Services / Cloud Code / Auth / Economy package and script inventory.",
inputSchema: {},
}, async () => json(R.getUgsCloudStack(projectRoot)));
server.registerTool("get_accessibility_stack", {
description: "Accessibility module/package presence and AccessibilityNode / AssistiveSupport script hits.",
inputSchema: {},
}, async () => json(R.getAccessibilityStack(projectRoot)));
server.registerTool("list_cinemachine_assets", {
description: "Cinemachine package + virtual camera assets/prefabs under Assets.",
inputSchema: {},
}, async () => json(R.listCinemachineAssets(projectRoot)));
server.registerTool("get_platform_build_hints", {
description: "PlayerSettings snippets for Android/WebGL/etc., build target, and related packages (Unity 6 platform pass).",
inputSchema: {},
}, async () => json(R.getPlatformBuildHints(projectRoot)));
server.registerTool("get_render_pipeline_migration_hints", {
description: "Infer BIRP vs URP/HDRP and flag Built-in RP deprecation / migration risk (Unity 6.5+).",
inputSchema: {},
}, async () => json(R.getRenderPipelineMigrationHints(projectRoot)));
server.registerTool("get_localization_stack", {
description: "Localization package + Locale/StringTable assets (broader than get_localization_tables).",
inputSchema: {},
}, async () => json(R.getLocalizationStack(projectRoot)));
// --- AI / ML / inference ---

@@ -402,8 +444,8 @@ server.registerTool("get_ai_ml_package_inventory", {

server.registerTool("list_ai_skills", {
description: "List bundled Unity AI agent skills shipped with unity-mcp-server (ML-Agents, Sentis, NavMesh NPC, LLM integration, audit workflows).",
description: "List bundled workflow skills by Unity domain (scenes, prefabs, assets, project, code, ai-ml, performance, …).",
inputSchema: {},
}, async () => json(R.listBundledAiSkills()));
server.registerTool("read_ai_skill", {
description: "Read a bundled Unity AI skill by id (from list_ai_skills). Teaches agents how to work on Unity AI features.",
inputSchema: { skill_id: z.string().describe("Skill folder id, e.g. unity-ml-agents") },
description: "Read a bundled skill by id from list_ai_skills (e.g. unity-scenes-workflow, unity-runtime-performance).",
inputSchema: { skill_id: z.string().describe("Skill folder id, e.g. unity-scenes-workflow or unity-ml-agents") },
}, async (args) => {

@@ -410,0 +452,0 @@ const skill = R.readBundledAiSkill(args.skill_id);

+7
-7
/**
* Addressables & localization readers.
*/
import { readdirSync, existsSync } from "node:fs";
import { readdirSync } from "node:fs";
import { join } from "node:path";
import { ASSETS } from "./helpers.js";
import { ASSETS, resolveUnderRoot } from "./helpers.js";
export function getAddressablesInfo(root) {
const path = join(ASSETS, "AddressableAssetsData");
const full = join(root, path);
if (!existsSync(full))
const full = resolveUnderRoot(root, path);
if (!full)
return { groups: [], configPath: null };

@@ -32,4 +32,4 @@ let entries;

const locDir = join(ASSETS, "Localization");
const full = join(root, locDir);
if (!existsSync(full))
const full = resolveUnderRoot(root, locDir);
if (!full)
return [];

@@ -39,3 +39,3 @@ try {

if (e.endsWith(".asset") || e.endsWith(".csv") || e.endsWith(".json"))
tables.push(join(locDir, e));
tables.push(join(locDir, e).split(/[/\\]/).join("/"));
}

@@ -42,0 +42,0 @@ }

@@ -5,7 +5,7 @@ /**

*/
import { readdirSync, existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { readdirSync, existsSync, readFileSync, realpathSync, statSync } from "node:fs";
import { join, relative, isAbsolute } from "node:path";
import { fileURLToPath } from "node:url";
import { dirname } from "node:path";
import { readFileSafe, listFilesRecursive, ASSETS } from "./helpers.js";
import { readFileSafe, listFilesRecursive, truncateText, ASSETS } from "./helpers.js";
import { getPackages } from "./project.js";

@@ -214,30 +214,80 @@ import { listScripts } from "./code.js";

const SKILLS_DIR = join(SERVER_ROOT, "skills");
/** Category folder under skills/ (e.g. ai-ml, shipping). Leaf folder name is the skill id. */
function findSkillMarkdownFiles(dir) {
const out = [];
if (!existsSync(dir))
return out;
const walk = (abs, category) => {
let entries;
try {
entries = readdirSync(abs, { withFileTypes: true });
}
catch {
return;
}
for (const ent of entries) {
if (!ent.isDirectory() || ent.name.startsWith("."))
continue;
const child = join(abs, ent.name);
const skillMd = join(child, "SKILL.md");
if (existsSync(skillMd)) {
out.push({ id: ent.name, category: category || ent.name, skillPath: skillMd });
}
else {
// Category directory — recurse one level (or deeper)
walk(child, category || ent.name);
}
}
};
walk(dir, "");
return out;
}
export function listBundledAiSkills() {
if (!existsSync(SKILLS_DIR))
return [];
const entries = readdirSync(SKILLS_DIR, { withFileTypes: true });
const found = findSkillMarkdownFiles(SKILLS_DIR);
const skills = [];
for (const ent of entries) {
if (!ent.isDirectory())
continue;
const skillPath = join(SKILLS_DIR, ent.name, "SKILL.md");
if (!existsSync(skillPath))
continue;
const raw = readFileSync(skillPath, "utf8");
const nameMatch = raw.match(/^name:\s*(.+)$/m);
const descMatch = raw.match(/^description:\s*>?-?\s*(.+)$/m);
skills.push({
id: ent.name,
name: nameMatch?.[1]?.trim() ?? ent.name,
description: descMatch?.[1]?.trim() ?? "Unity AI workflow skill",
});
for (const f of found) {
try {
const raw = readFileSync(f.skillPath, "utf8");
const nameMatch = raw.match(/^name:\s*(.+)$/m);
const descMatch = raw.match(/^description:\s*>?-?\s*(.+)$/m);
skills.push({
id: f.id,
name: nameMatch?.[1]?.trim() ?? f.id,
description: descMatch?.[1]?.trim() ?? "Unity workflow skill",
category: f.category,
});
}
catch {
/* skip unreadable */
}
}
return skills.sort((a, b) => a.id.localeCompare(b.id));
return skills.sort((a, b) => {
const c = (a.category || "").localeCompare(b.category || "");
return c !== 0 ? c : a.id.localeCompare(b.id);
});
}
export function readBundledAiSkill(skillId) {
const safe = skillId.replace(/[^a-z0-9-]/gi, "");
const skillPath = join(SKILLS_DIR, safe, "SKILL.md");
if (!existsSync(skillPath))
if (!safe)
return null;
return { id: safe, content: readFileSync(skillPath, "utf8") };
const match = findSkillMarkdownFiles(SKILLS_DIR).find((f) => f.id === safe);
if (!match)
return null;
try {
if (!existsSync(SKILLS_DIR) || !existsSync(match.skillPath))
return null;
const realSkills = realpathSync(SKILLS_DIR);
const realSkill = realpathSync(match.skillPath);
const rel = relative(realSkills, realSkill);
if (rel.startsWith("..") || isAbsolute(rel) || !statSync(realSkill).isFile())
return null;
return {
id: safe,
category: match.category,
content: truncateText(readFileSync(realSkill, "utf8")),
};
}
catch {
return null;
}
}
/**
* Assets & references readers.
*/
import { existsSync, readdirSync, statSync, readFileSync } from "node:fs";
import { readdirSync, statSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { listFilesRecursive, readFileSafe, getAssetPathByGuid, getGuidFromMeta, findReferencesToGuid, ASSETS } from "./helpers.js";
import { listFilesRecursive, readFileSafe, getAssetPathByGuid, getGuidFromMeta, findReferencesToGuid, resolveUnderRoot, wildcardRegExp, ASSETS } from "./helpers.js";
import { findScriptsByContent } from "./code.js";
export function getAssetFolderTree(root, maxDepth = 4) {
const assetsDir = join(root, ASSETS);
if (!existsSync(assetsDir))
if (!resolveUnderRoot(root, ASSETS))
return {};

@@ -16,3 +15,5 @@ const result = {};

return [];
const full = join(root, dir);
const full = resolveUnderRoot(root, dir);
if (!full)
return [];
let entries;

@@ -27,6 +28,8 @@ try {

for (const e of entries) {
if (e.startsWith("."))
if (e.startsWith(".") || e === ".." || e === ".")
continue;
const rel = join(dir, e);
const fullPath = join(root, rel);
const fullPath = resolveUnderRoot(root, rel);
if (!fullPath)
continue;
if (statSync(fullPath).isDirectory()) {

@@ -48,3 +51,6 @@ children.push(rel + "/");

export function listAssetsByExtension(root, ext, folder) {
// Confine optional folder under Assets (blocks ../ escape).
const dir = folder ? join(ASSETS, folder) : ASSETS;
if (!resolveUnderRoot(root, dir))
return [];
return listFilesRecursive(root, dir, { ext: ext.toLowerCase() });

@@ -55,10 +61,18 @@ }

const threshold = minSizeMb * 1024 * 1024;
if (!resolveUnderRoot(root, ASSETS))
return out;
const stack = [ASSETS];
while (stack.length) {
const d = stack.pop();
const fullD = join(root, d);
const fullD = resolveUnderRoot(root, d);
if (!fullD)
continue;
try {
for (const e of readdirSync(fullD)) {
if (e === "." || e === "..")
continue;
const rel = join(d, e);
const fullPath = join(root, rel);
const fullPath = resolveUnderRoot(root, rel);
if (!fullPath)
continue;
if (statSync(fullPath).isDirectory()) {

@@ -71,3 +85,3 @@ if (!e.startsWith("."))

if (size >= threshold)
out.push({ path: rel, sizeMb: Math.round((size / 1024 / 1024) * 100) / 100 });
out.push({ path: rel.split(/[/\\]/).join("/"), sizeMb: Math.round((size / 1024 / 1024) * 100) / 100 });
}

@@ -122,10 +136,18 @@ }

const scriptGuids = new Set();
if (!resolveUnderRoot(root, ASSETS))
return [];
const stack = [ASSETS];
while (stack.length) {
const d = stack.pop();
const fullD = join(root, d);
const fullD = resolveUnderRoot(root, d);
if (!fullD)
continue;
try {
for (const e of readdirSync(fullD)) {
if (e === "." || e === "..")
continue;
const rel = join(d, e);
const fullPath = join(root, rel);
const fullPath = resolveUnderRoot(root, rel);
if (!fullPath)
continue;
if (statSync(fullPath).isDirectory()) {

@@ -218,11 +240,11 @@ if (!e.startsWith("."))

}
/** Search Assets (and optionally Packages) by file or folder name pattern (e.g. "Player", "*Menu*"). */
/** Search Assets (and optionally Packages) by file or folder name pattern (e.g. "Player", "*Menu*"). ReDoS-safe. */
export function searchAssetsByName(root, namePattern, includePackages) {
const pattern = namePattern.replace(/\*/g, ".*").toLowerCase();
const re = new RegExp(pattern, "i");
const re = wildcardRegExp(namePattern.includes("*") ? namePattern : `*${namePattern}*`, "i");
if (!re)
return [];
const out = [];
const dirs = includePackages ? [ASSETS, "Packages"] : [ASSETS];
for (const dir of dirs) {
const full = join(root, dir);
if (!existsSync(full) || !statSync(full).isDirectory())
if (!resolveUnderRoot(root, dir))
continue;

@@ -232,7 +254,14 @@ const stack = [dir];

const d = stack.pop();
const fullD = join(root, d);
const fullD = resolveUnderRoot(root, d);
if (!fullD)
continue;
try {
for (const e of readdirSync(fullD)) {
if (e === "." || e === "..")
continue;
const rel = join(d, e);
const fullPath = join(root, rel);
const fullPath = resolveUnderRoot(root, rel);
if (!fullPath)
continue;
const relNorm = rel.split(/[/\\]/).join("/");
if (statSync(fullPath).isDirectory()) {

@@ -242,8 +271,8 @@ if (!e.startsWith(".") && e !== "node_modules")

if (re.test(e))
out.push(rel + "/");
out.push(relNorm + "/");
}
else {
const nameWithoutMeta = e.endsWith(".meta") ? e.slice(0, -5) : e;
if (re.test(nameWithoutMeta) || re.test(rel))
out.push(rel);
if (re.test(nameWithoutMeta) || re.test(relNorm))
out.push(relNorm);
}

@@ -348,13 +377,18 @@ }

const validGuids = new Set();
if (!resolveUnderRoot(root, ASSETS))
return [];
const stack = [ASSETS];
const fullRoot = join(root, ASSETS);
if (!existsSync(fullRoot))
return [];
while (stack.length) {
const d = stack.pop();
const fullD = join(root, d);
const fullD = resolveUnderRoot(root, d);
if (!fullD)
continue;
try {
for (const e of readdirSync(fullD)) {
if (e === "." || e === "..")
continue;
const rel = join(d, e);
const fullPath = join(root, rel);
const fullPath = resolveUnderRoot(root, rel);
if (!fullPath)
continue;
if (statSync(fullPath).isDirectory()) {

@@ -361,0 +395,0 @@ if (!e.startsWith("."))

/**
* CI & version control readers.
*/
import { readdirSync, existsSync } from "node:fs";
import { join } from "node:path";
import { listFilesRecursive, readFileSafe, ASSETS } from "./helpers.js";
import { readdirSync } from "node:fs";
import { listFilesRecursive, readFileSafe, resolveUnderRoot, ASSETS } from "./helpers.js";
export function listCiConfigs(root) {
const out = [];
const gh = join(root, ".github", "workflows");
if (existsSync(gh))
readdirSync(gh).filter((e) => e.endsWith(".yml") || e.endsWith(".yaml")).forEach((e) => out.push(`.github/workflows/${e}`));
if (existsSync(join(root, "Jenkinsfile")))
const gh = resolveUnderRoot(root, ".github", "workflows");
if (gh) {
try {
readdirSync(gh)
.filter((e) => e.endsWith(".yml") || e.endsWith(".yaml"))
.forEach((e) => out.push(`.github/workflows/${e}`));
}
catch {
/* */
}
}
if (resolveUnderRoot(root, "Jenkinsfile"))
out.push("Jenkinsfile");
if (existsSync(join(root, "unity-cloud-build.json")))
if (resolveUnderRoot(root, "unity-cloud-build.json"))
out.push("unity-cloud-build.json");

@@ -28,3 +35,3 @@ return out;

export function getPlasticConfig(root) {
const plasticDir = existsSync(join(root, ".plastic"));
const plasticDir = !!resolveUnderRoot(root, ".plastic");
let workspaceName;

@@ -31,0 +38,0 @@ const conf = readFileSafe(root, ".plastic", "plastic.workspace");

/**
* Code & assemblies readers.
*/
import { readJsonSafe, readFileSafe, listFilesRecursive, ASSETS } from "./helpers.js";
import { readJsonSafe, readFileSafe, listFilesRecursive, literalRegExp, wildcardRegExp, resolveUnderRoot, ASSETS } from "./helpers.js";
import { join } from "node:path";
import { existsSync } from "node:fs";
export function getAssemblyDefinitions(root) {

@@ -28,2 +27,4 @@ const files = listFilesRecursive(root, ASSETS, { ext: ".asmdef" });

const prefix = folderPrefix.startsWith("Assets/") ? folderPrefix : join(ASSETS, folderPrefix);
if (!resolveUnderRoot(root, prefix))
return [];
files = files.filter((f) => f.startsWith(prefix + "/") || f.startsWith(prefix + "\\"));

@@ -33,7 +34,13 @@ }

}
/** Simple grep for type/namespace in .cs file content. */
/** Simple grep for type/namespace in .cs file content (ReDoS-safe: literal / wildcard only). */
export function findScriptsByContent(root, pattern, namespaceFilter) {
const files = listScripts(root);
const re = new RegExp(pattern, "i");
const nsRe = namespaceFilter ? new RegExp(namespaceFilter.replace(/\*/g, ".*"), "i") : null;
const re = literalRegExp(pattern, "i");
if (!re)
return [];
const nsRe = namespaceFilter
? (namespaceFilter.includes("*") ? wildcardRegExp(namespaceFilter, "i") : literalRegExp(namespaceFilter, "i"))
: null;
if (namespaceFilter && !nsRe)
return [];
const out = [];

@@ -67,3 +74,3 @@ for (const rel of files) {

for (const dir of vsPaths) {
if (existsSync(join(root, dir)))
if (resolveUnderRoot(root, dir))
out.push(...listFilesRecursive(root, dir, { ext: ".asset" }));

@@ -70,0 +77,0 @@ }

/**
* Shared helper utilities for Unity project filesystem readers.
* All project-relative FS access is confined under the Unity project root.
*/
import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
import { join, extname } from "node:path";
import { readFileSync, existsSync, readdirSync, statSync, realpathSync } from "node:fs";
import { join, extname, resolve, relative, isAbsolute, dirname, sep } from "node:path";
export const ASSETS = "Assets";
export const PROJECT_SETTINGS = "ProjectSettings";
export const PACKAGES = "Packages";
export function readFileSafe(root, ...path) {
const p = join(root, ...path);
if (!existsSync(p))
/** Soft cap for docs/skills/agent markdown returned to the MCP client. */
export const MAX_TEXT_CHARS = 200_000;
/** Soft cap for user-supplied search / regex-like patterns. */
export const MAX_PATTERN_CHARS = 200;
export function truncateText(s, max = MAX_TEXT_CHARS) {
if (s.length <= max)
return s;
return `${s.slice(0, max)}\n\n… [truncated ${s.length - max} chars for safety]`;
}
export function escapeRegExp(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
/** Compile a literal pattern (all metacharacters escaped). Returns null if too long/empty. */
export function literalRegExp(pattern, flags = "i") {
if (!pattern || pattern.length > MAX_PATTERN_CHARS)
return null;
return new RegExp(escapeRegExp(pattern), flags);
}
/**
* Compile a wildcard pattern where `*` means "any chars".
* All other regex metacharacters are escaped (ReDoS-safe).
*/
export function wildcardRegExp(pattern, flags = "i") {
if (!pattern || pattern.length > MAX_PATTERN_CHARS)
return null;
const escaped = escapeRegExp(pattern).replace(/\\\*/g, ".*");
return new RegExp(`^${escaped}$`, flags);
}
function realRoot(root) {
try {
const abs = resolve(root);
return existsSync(abs) ? realpathSync(abs) : abs;
}
catch {
return null;
}
}
/**
* Resolve path segments under `root`. Returns absolute path only if the result
* stays inside the project root (symlink-aware via realpath).
*/
export function resolveUnderRoot(root, ...segments) {
if (!root)
return null;
for (const s of segments) {
if (typeof s !== "string" || s.includes("\0") || isAbsolute(s))
return null;
}
const rootAbs = realRoot(root);
if (!rootAbs)
return null;
const candidate = resolve(rootAbs, ...segments);
const relLogical = relative(rootAbs, candidate);
if (relLogical.startsWith("..") || isAbsolute(relLogical))
return null;
try {
if (existsSync(candidate)) {
const realCand = realpathSync(candidate);
const relReal = relative(rootAbs, realCand);
if (relReal.startsWith("..") || isAbsolute(relReal))
return null;
return realCand;
}
// Missing path: ensure every existing ancestor stays inside root (blocks symlink hops).
let cur = dirname(candidate);
for (;;) {
if (existsSync(cur)) {
const realCur = realpathSync(cur);
const relCur = relative(rootAbs, realCur);
if (relCur.startsWith("..") || isAbsolute(relCur))
return null;
break;
}
if (cur === rootAbs || !cur.startsWith(rootAbs + sep))
break;
const parent = dirname(cur);
if (parent === cur)
break;
cur = parent;
}
}
catch {
return null;
}
return candidate;
}
/** True if absolutePath resolves inside root (symlink-aware). */
export function isInsideRoot(root, absolutePath) {
const rootAbs = realRoot(root);
if (!rootAbs)
return false;
try {
const cand = resolve(absolutePath);
const rel = relative(rootAbs, cand);
if (rel.startsWith("..") || isAbsolute(rel))
return false;
if (!existsSync(cand))
return true;
const realCand = realpathSync(cand);
const rel2 = relative(rootAbs, realCand);
return !(rel2.startsWith("..") || isAbsolute(rel2));
}
catch {
return false;
}
}
export function readFileSafe(root, ...pathSegments) {
const p = resolveUnderRoot(root, ...pathSegments);
if (!p || !existsSync(p))
return null;
try {
if (!statSync(p).isFile())
return null;
return readFileSync(p, "utf-8");

@@ -20,4 +129,9 @@ }

}
export function readJsonSafe(root, ...path) {
const s = readFileSafe(root, ...path);
/** Like readFileSafe but truncates large text (docs / skills / agent markdown). */
export function readTextCapped(root, ...pathSegments) {
const s = readFileSafe(root, ...pathSegments);
return s == null ? null : truncateText(s);
}
export function readJsonSafe(root, ...pathSegments) {
const s = readFileSafe(root, ...pathSegments);
if (!s)

@@ -34,10 +148,12 @@ return null;

export function listFilesRecursive(root, dir, opts = {}) {
const full = join(root, dir);
if (!existsSync(full) || !statSync(full).isDirectory())
const startAbs = resolveUnderRoot(root, dir);
if (!startAbs || !existsSync(startAbs) || !statSync(startAbs).isDirectory())
return [];
const out = [];
const stack = [dir];
const stack = [dir.split(/[/\\]/).filter(Boolean).join(sep) || dir];
while (stack.length) {
const d = stack.pop();
const fullD = join(root, d);
const fullD = resolveUnderRoot(root, d);
if (!fullD)
continue;
let entries;

@@ -51,14 +167,23 @@ try {

for (const e of entries) {
if (e === "." || e === "..")
continue;
const rel = join(d, e);
const fullPath = join(root, rel);
if (statSync(fullPath).isDirectory()) {
if (e !== "node_modules" && e !== ".git" && !e.startsWith("."))
stack.push(rel);
const fullPath = resolveUnderRoot(root, rel);
if (!fullPath)
continue;
try {
if (statSync(fullPath).isDirectory()) {
if (e !== "node_modules" && e !== ".git" && !e.startsWith("."))
stack.push(rel);
}
else {
if (opts.excludeMeta && e.endsWith(".meta"))
continue;
if (opts.ext && extname(e).toLowerCase() !== opts.ext)
continue;
out.push(rel.split(/[/\\]/).join("/"));
}
}
else {
if (opts.excludeMeta && e.endsWith(".meta"))
continue;
if (opts.ext && extname(e).toLowerCase() !== opts.ext)
continue;
out.push(rel);
catch {
continue;
}

@@ -92,4 +217,3 @@ }

export function getAssetPathByGuid(root, guid) {
const assetsDir = join(root, ASSETS);
if (!existsSync(assetsDir))
if (!resolveUnderRoot(root, ASSETS))
return null;

@@ -99,3 +223,5 @@ const stack = [ASSETS];

const d = stack.pop();
const fullD = join(root, d);
const fullD = resolveUnderRoot(root, d);
if (!fullD)
continue;
let entries;

@@ -109,13 +235,22 @@ try {

for (const e of entries) {
if (e === "." || e === "..")
continue;
const rel = join(d, e);
const fullPath = join(root, rel);
if (statSync(fullPath).isDirectory()) {
if (!e.startsWith("."))
stack.push(rel);
const fullPath = resolveUnderRoot(root, rel);
if (!fullPath)
continue;
try {
if (statSync(fullPath).isDirectory()) {
if (!e.startsWith("."))
stack.push(rel);
}
else if (e.endsWith(".meta")) {
const content = readFileSync(fullPath, "utf-8");
const m = content.match(/^guid:\s*([a-f0-9]{32})/m);
if (m && m[1] === guid)
return rel.replace(/\.meta$/, "").split(/[/\\]/).join("/");
}
}
else if (e.endsWith(".meta")) {
const content = readFileSync(fullPath, "utf-8");
const m = content.match(/^guid:\s*([a-f0-9]{32})/m);
if (m && m[1] === guid)
return rel.replace(/\.meta$/, "");
catch {
continue;
}

@@ -130,4 +265,3 @@ }

const exts = [".unity", ".prefab", ".asset", ".mat", ".controller", ".anim", ".mixer", ".overrideController"];
const assetsDir = join(root, ASSETS);
if (!existsSync(assetsDir))
if (!resolveUnderRoot(root, ASSETS))
return found;

@@ -137,7 +271,13 @@ const stack = [ASSETS];

const d = stack.pop();
const fullD = join(root, d);
const fullD = resolveUnderRoot(root, d);
if (!fullD)
continue;
try {
for (const e of readdirSync(fullD)) {
if (e === "." || e === "..")
continue;
const rel = join(d, e);
const fullPath = join(root, rel);
const fullPath = resolveUnderRoot(root, rel);
if (!fullPath)
continue;
if (statSync(fullPath).isDirectory()) {

@@ -150,3 +290,3 @@ if (!e.startsWith("."))

if (content.includes(guid))
found.push(rel);
found.push(rel.split(/[/\\]/).join("/"));
}

@@ -153,0 +293,0 @@ }

@@ -43,1 +43,3 @@ /**

export * from "./ai.js";
// Unity 6.x coverage (multiplayer, build profiles, physics, UGS, …)
export * from "./unity6.js";
/**
* Third-party integrations readers.
*/
import { readdirSync, existsSync } from "node:fs";
import { readdirSync } from "node:fs";
import { join } from "node:path";
import { listFilesRecursive, readFileSafe, ASSETS } from "./helpers.js";
import { listFilesRecursive, readFileSafe, resolveUnderRoot, ASSETS } from "./helpers.js";
import { getPackages } from "./project.js";

@@ -25,3 +25,3 @@ export function getPlayFabConfig(root) {

const figmaDir = join(ASSETS, "Figma");
if (existsSync(join(root, figmaDir)))
if (resolveUnderRoot(root, figmaDir))
return listFilesRecursive(root, figmaDir);

@@ -52,4 +52,5 @@ return listFilesRecursive(root, ASSETS).filter((p) => p.toLowerCase().includes("figma"));

const plug = join(ASSETS, "Plugins");
if (existsSync(join(root, plug))) {
const entries = readdirSync(join(root, plug));
const plugAbs = resolveUnderRoot(root, plug);
if (plugAbs) {
const entries = readdirSync(plugAbs);
if (entries.some((e) => e.toLowerCase().includes("steam")))

@@ -62,4 +63,4 @@ steamworksPath = join(plug, entries.find((e) => e.toLowerCase().includes("steam")));

const plug = join(ASSETS, "Plugins");
const full = join(root, plug);
if (!existsSync(full))
const full = resolveUnderRoot(root, plug);
if (!full)
return {};

@@ -99,3 +100,3 @@ const entries = readdirSync(full);

const lottieDir = join(ASSETS, "Lottie");
if (existsSync(join(root, lottieDir)))
if (resolveUnderRoot(root, lottieDir))
return listFilesRecursive(root, lottieDir, { ext: ".json" });

@@ -102,0 +103,0 @@ return listFilesRecursive(root, ASSETS, { ext: ".json" }).filter((p) => p.toLowerCase().includes("lottie"));

/**
* Materials & shaders readers.
*/
import { listFilesRecursive, readFileSafe, ASSETS, PACKAGES } from "./helpers.js";
import { listFilesRecursive, readFileSafe, resolveUnderRoot, ASSETS, PACKAGES } from "./helpers.js";
import { join } from "node:path";

@@ -10,2 +10,4 @@ export function getMaterials(root, folder) {

const prefix = folder.startsWith("Assets/") ? folder : join(ASSETS, folder);
if (!resolveUnderRoot(root, prefix))
return [];
files = files.filter((f) => f.startsWith(prefix + "/") || f.startsWith(prefix + "\\"));

@@ -12,0 +14,0 @@ }

@@ -7,3 +7,3 @@ /**

import { homedir, platform } from "node:os";
import { readJsonSafe, readFileSafe, parseUnityKeyValue, listFilesRecursive, ASSETS, PROJECT_SETTINGS, PACKAGES } from "./helpers.js";
import { readJsonSafe, readFileSafe, readTextCapped, parseUnityKeyValue, listFilesRecursive, ASSETS, PROJECT_SETTINGS, PACKAGES } from "./helpers.js";
export function getUnityVersion(root) {

@@ -100,3 +100,3 @@ const s = readFileSafe(root, PROJECT_SETTINGS, "ProjectVersion.txt");

export function getChangelog(root) {
return readFileSafe(root, "CHANGELOG.md") || readFileSafe(root, "CHANGELOG") || readFileSafe(root, "changelog.md");
return readTextCapped(root, "CHANGELOG.md") || readTextCapped(root, "CHANGELOG") || readTextCapped(root, "changelog.md");
}

@@ -294,6 +294,7 @@ export function getPhysicsSettings(root) {

}
/** List Unity projects from Unity Hub's projects list (if available). Paths are OS-specific. */
/** List Unity Hub projects. Full absolute paths are redacted unless UNITY_MCP_ALLOW_HUB_PATHS=1. */
export function listUnityHubProjects() {
const home = homedir();
const out = [];
const allowFullPaths = process.env.UNITY_MCP_ALLOW_HUB_PATHS === "1";
const candidates = [];

@@ -322,5 +323,11 @@ if (platform() === "darwin") {

const rec = item;
const path = rec.path ?? rec.projectPath;
if (path && typeof path === "string")
out.push({ path, name: rec.name, source: p });
const fullPath = rec.path ?? rec.projectPath;
if (fullPath && typeof fullPath === "string") {
const base = fullPath.split(/[/\\]/).filter(Boolean).pop() || fullPath;
out.push({
path: allowFullPaths ? fullPath : base,
name: rec.name ?? base,
source: allowFullPaths ? p : "Unity Hub (path redacted; set UNITY_MCP_ALLOW_HUB_PATHS=1 for full paths)",
});
}
}

@@ -327,0 +334,0 @@ break;

/**
* Scenes & prefabs readers.
*/
import { readFileSafe, listFilesRecursive, ASSETS } from "./helpers.js";
import { readFileSafe, listFilesRecursive, resolveUnderRoot, ASSETS } from "./helpers.js";
import { getSceneReferencedAssets } from "./assets.js";

@@ -33,2 +33,4 @@ import { join } from "node:path";

const prefix = pathPrefix.startsWith("Assets/") ? pathPrefix : join(ASSETS, pathPrefix);
if (!resolveUnderRoot(root, prefix))
return [];
files = files.filter((f) => f.startsWith(prefix + "/") || f.startsWith(prefix + "\\"));

@@ -35,0 +37,0 @@ }

@@ -5,3 +5,2 @@ /**

import { statSync } from "node:fs";
import { join } from "node:path";
import { listScripts, getAssemblyDefinitions, getAssemblyDependencyGraph } from "./code.js";

@@ -13,3 +12,3 @@ import { getPrefabs, getAllScenes } from "./scenes.js";

import { getSceneReferencedAssets, getBrokenScriptRefs, listLargeAssets } from "./assets.js";
import { readFileSafe } from "./helpers.js";
import { readFileSafe, resolveUnderRoot } from "./helpers.js";
/** Project stats in one call (script count, prefab count, etc.). */

@@ -114,3 +113,5 @@ export function getProjectStats(root) {

for (const rel of allPaths) {
const full = join(root, rel);
const full = resolveUnderRoot(root, rel);
if (!full)
continue;
try {

@@ -117,0 +118,0 @@ const size = statSync(full).size;

/**
* Testing & docs readers.
*/
import { readFileSafe } from "./helpers.js";
import { readTextCapped } from "./helpers.js";
import { getAssemblyDefinitions } from "./code.js";

@@ -10,3 +10,3 @@ const DOC_FILES = ["README.md", "CONTRIBUTING.md", ".cursorrules", "CODING_STANDARDS.md", "STYLE.md"];

for (const name of DOC_FILES) {
const s = readFileSafe(root, name);
const s = readTextCapped(root, name);
if (s)

@@ -13,0 +13,0 @@ out[name] = s;

/**
* TextMeshPro & UI Toolkit readers.
*/
import { listFilesRecursive, ASSETS } from "./helpers.js";
import { existsSync } from "node:fs";
import { join } from "node:path";
import { listFilesRecursive, resolveUnderRoot, ASSETS } from "./helpers.js";
export function listTmpFonts(root) {

@@ -13,3 +11,3 @@ return listFilesRecursive(root, ASSETS, { ext: ".asset" }).filter((p) => p.toLowerCase().includes("tmp") || p.toLowerCase().includes("font"));

for (const p of paths)
if (existsSync(join(root, p)))
if (resolveUnderRoot(root, p))
return p;

@@ -16,0 +14,0 @@ return null;

@@ -124,4 +124,14 @@ export const TOOLS_CATALOG = [

{ name: "get_ai_stack_summary", description: "One-shot AI stack: packages, models, scripts, configs", category: "AI & ML" },
{ name: "list_ai_skills", description: "Bundled Unity AI agent skills (workflows)", category: "AI & ML" },
{ name: "read_ai_skill", description: "Read bundled skill by id (unity-ml-agents, etc.)", category: "AI & ML" },
{ name: "list_ai_skills", description: "Bundled skills by Unity domain (scenes, prefabs, assets, ai-ml, …)", category: "AI & ML" },
{ name: "read_ai_skill", description: "Read skill by id (e.g. unity-scenes-workflow, unity-runtime-performance)", category: "AI & ML" },
{ name: "get_multiplayer_stack", description: "Netcode / multiplayer packages and scripts", category: "Multiplayer & services" },
{ name: "list_build_profiles", description: "Unity 6 Build Profile assets", category: "Project & build" },
{ name: "get_physics_stack", description: "Unity Physics / Physics Core 2D / classic physics inventory", category: "Project & build" },
{ name: "get_optimization_package_inventory", description: "Adaptive Performance + Project Auditor", category: "Speed & productivity" },
{ name: "get_ugs_cloud_stack", description: "Unity Gaming Services / Cloud Code inventory", category: "Multiplayer & services" },
{ name: "get_accessibility_stack", description: "Accessibility APIs and packages", category: "Project & build" },
{ name: "list_cinemachine_assets", description: "Cinemachine cameras and related assets", category: "Animation" },
{ name: "get_platform_build_hints", description: "Android/WebGL PlayerSettings + platform packages", category: "Project & build" },
{ name: "get_render_pipeline_migration_hints", description: "BIRP vs URP/HDRP migration risk", category: "Rendering" },
{ name: "get_localization_stack", description: "Localization package + locale/string table assets", category: "Addressables & localization" },
];

@@ -128,0 +138,0 @@ export function searchToolsCatalog(query) {

{
"name": "unity-mcp-server",
"version": "1.6.1",
"description": "Lightweight MCP server for Unity - project info, AI/ML discovery, agent skills, 110+ tools (no Unity Editor required)",
"version": "1.7.0",
"description": "Lightweight MCP server for Unity - project info, Unity 6 coverage, AI/ML discovery, 39 agent skills, 130+ tools (no Unity Editor required)",
"type": "module",

@@ -6,0 +6,0 @@ "main": "dist/index.js",

@@ -8,3 +8,3 @@ # Unity MCP Server

<p align="center">
<img src="assets/unity-mcp-server-diagram.png" alt="Cursor / IDE → Unity MCP Server → Unity Project" width="720">
<img src="https://raw.githubusercontent.com/rachitkumarrastogi/unity-mcp-server/main/assets/unity-mcp-server-diagram.png" alt="Cursor / IDE → Unity MCP Server → Unity Project" width="720">
</p>

@@ -289,5 +289,23 @@

| `get_ads_config` | Unity Ads, AdMob, ironSource presence | *"Is ads SDK configured?"* |
| `get_multiplayer_stack` | Netcode / multiplayer packages and scripts | *"What multiplayer stack does this use?"* |
| `get_ugs_cloud_stack` | Unity Gaming Services / Cloud Code inventory | *"Is UGS or Cloud Code set up?"* |
</details>
<details id="unity6">
<summary><strong>🧱 Unity 6 coverage</strong></summary>
| Tool | Description | Example prompt to type |
|------|-------------|-------------------------|
| `list_build_profiles` | Unity 6 Build Profile assets | *"List build profiles"* |
| `get_physics_stack` | Unity Physics / Physics Core 2D / classic physics | *"Audit the physics stack"* |
| `get_optimization_package_inventory` | Adaptive Performance + Project Auditor | *"Is Adaptive Performance installed?"* |
| `get_accessibility_stack` | Accessibility APIs and packages | *"Any accessibility APIs in use?"* |
| `list_cinemachine_assets` | Cinemachine cameras and related assets | *"List Cinemachine assets"* |
| `get_platform_build_hints` | Android/WebGL PlayerSettings snippets | *"Show platform build hints"* |
| `get_render_pipeline_migration_hints` | BIRP vs URP/HDRP migration risk | *"Are we still on Built-in RP?"* |
| `get_localization_stack` | Localization package + locale tables | *"Audit localization setup"* |
</details>
<details id="speed">

@@ -320,4 +338,4 @@ <summary><strong>⚡ Speed & productivity</strong></summary>

| `list_ai_prompt_or_config_assets` | Prompt/RAG-like JSON or MD under Assets | *"Any LLM prompt assets in Assets?"* |
| `list_ai_skills` | Bundled Unity AI workflow skills | *"List Unity AI skills"* |
| `read_ai_skill` | Read skill by id (`unity-ml-agents`, `unity-sentis-inference`, …) | *"Read the unity-ai-audit skill"* |
| `list_ai_skills` | Bundled skills by Unity domain (39: scenes, multiplayer, build profiles, …) | *"List Unity skills"* |
| `read_ai_skill` | Read skill by id (e.g. `unity-scenes-workflow`, `unity-multiplayer-netcode`) | *"Read the unity-ai-audit skill"* |

@@ -437,4 +455,4 @@ </details>

- [**MCP Registry**](https://registry.modelcontextprotocol.io/?q=unity-mcp-server) — Discover and install this server from the official registry.
- **Guides:** [Quick start (end-to-end in your Unity project)](./docs/guides/QUICK_START_UNITY_CODEBASE.md) · [Purpose and use cases](./docs/guides/PURPOSE.md) · [Publish to npm and MCP Registry](./docs/guides/PUBLISH.md) · [How it helps Unity developers](./docs/guides/HOW_IT_HELPS_UNITY_DEVELOPERS.md) · [Tools by role](./docs/guides/TOOLS_BY_ROLE.md)
- **Reference:** [Registry details](./docs/reference/REGISTRY.md) · [Comparison and rating](./docs/reference/COMPARISON_AND_RATING.md) · [Audits and gap analysis](./docs/reference/README.md) · [Suggested tools to add](./docs/reference/SUGGESTED_TOOLS_TO_ADD.md)
- **Release notes:** [All versions](./docs/release-notes/README.md)
- **Guides:** [Quick start](./docs/guides/QUICK_START_UNITY_CODEBASE.md) · [Publish](./docs/guides/PUBLISH.md) · [Tools by role](./docs/guides/TOOLS_BY_ROLE.md)
- **Skills:** [Unity-domain agent skills](./skills/README.md)
- **Changelog:** [CHANGELOG.md](./CHANGELOG.md)

@@ -5,3 +5,3 @@ {

"title": "Unity MCP Server",
"description": "MCP server for Unity: 110+ tools, AI/ML discovery, bundled skills. No Editor.",
"description": "MCP server for Unity: 130+ tools, Unity 6 skills, AI/ML discovery. No Editor.",
"repository": {

@@ -11,3 +11,3 @@ "url": "https://github.com/rachitkumarrastogi/unity-mcp-server",

},
"version": "1.6.1",
"version": "1.7.0",
"packages": [

@@ -17,3 +17,3 @@ {

"identifier": "unity-mcp-server",
"version": "1.6.1",
"version": "1.7.0",
"transport": {

@@ -20,0 +20,0 @@ "type": "stdio"

# Publish to GitHub Packages when a release is published.
# npm package stays "unity-mcp-server" on npm; this publishes @rachitkumarrastogi/unity-mcp-server to GitHub Packages.
# See: https://docs.github.com/en/actions/how-tos/use-cases-and-examples/publishing-packages/publishing-nodejs-packages
name: Publish to GitHub Packages
on:
release:
types: [published]
jobs:
publish-gpr:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20.x"
registry-url: "https://npm.pkg.github.com"
scope: "@rachitkumarrastogi"
- run: npm ci
- run: npm run build
# GitHub Packages requires a scoped package name; we keep npm as "unity-mcp-server", so scope only for this publish
- run: node -e "const p=require('./package.json'); p.name='@rachitkumarrastogi/unity-mcp-server'; require('fs').writeFileSync('package.json', JSON.stringify(p,null,2));"
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Sorry, the diff of this file is not supported yet

---
name: unity-ai-audit
description: >-
Use to audit a Unity project's AI/ML stack: packages, models, scripts,
configs, and release risks. Start here for "what AI does this game use?"
---
# Unity AI stack audit
## One-shot
Run `get_ai_stack_summary` with `include_prompt_assets: true`.
## Deep dive order
| Step | Tool | Why |
|------|------|-----|
| 1 | `get_ai_ml_package_inventory` | Official Unity AI packages & versions |
| 2 | `list_ml_model_assets` | ONNX and other models in repo |
| 3 | `list_ml_agents_training_configs` | RL training YAMLs |
| 4 | `find_ai_related_scripts` | Code-level AI APIs |
| 5 | `list_ai_prompt_or_config_assets` | Prompt/RAG JSON or docs |
| 6 | `get_build_size_estimate` | Model weight in shipping build |
| 7 | `get_release_readiness` | Broken refs + cycles + large assets |
## Bundled skills
Call `list_ai_skills` then `read_ai_skill` for focused workflows:
- `unity-ml-agents`
- `unity-sentis-inference`
- `unity-navmesh-npc`
- `unity-llm-integration`
## Report template
```markdown
## AI stack summary
- Packages: ...
- Models: N ONNX, ...
- Scripts: N files (top labels: ...)
- Training configs: ...
- Risks: secrets in client?, build size, missing packages
```
---
name: unity-llm-integration
description: >-
Use when adding LLM/chat/RAG features to a Unity game or tool: API keys,
prompt assets, safety, latency, and server-side vs client-side calls.
---
# Unity LLM integration
## Discovery
1. `find_ai_related_scripts` — label **LLM / chat APIs**
2. `list_ai_prompt_or_config_assets` — JSON/MD with prompt-like keys under Assets
3. `get_ai_stack_summary` with `include_prompt_assets: true`
## Security & architecture
- **Never** commit API keys; use environment variables, Unity Cloud Code, or your backend proxy.
- Prefer **server-mediated** LLM calls for shipping titles (rate limits, moderation, cost).
- For editor-only tools, ScriptableObject prompts are OK if no secrets are embedded.
## UX patterns
- Stream responses to UI (TMP) with cancellation tokens.
- Cache embeddings locally only when license/privacy allows.
- Cap token usage per session for live games.
## Audit workflow
1. `find_ai_related_scripts` for hardcoded endpoints
2. `search_project` with `script_pattern: OpenAI` or `Anthropic`
3. Review `list_ai_prompt_or_config_assets` for PII in prompts
---
name: unity-ml-agents
description: >-
Use when working on Unity ML-Agents: training configs, BehaviorParameters,
sensors, actuators, inference in builds, or com.unity.ml-agents packages.
---
# Unity ML-Agents
## Discovery (unity-mcp-server)
1. `get_ai_stack_summary` — packages, ONNX assets, script hits, training YAMLs
2. `get_ai_ml_package_inventory` — confirm `com.unity.ml-agents` version
3. `list_ml_agents_training_configs` — trainer YAML paths
4. `find_ai_related_scripts` — filter label **ML-Agents**
## Implementation checklist
- **Training**: YAML defines `behaviors`, `trainer_type`, hyperparameters; keep configs in repo root or `config/`.
- **Inference**: Prefabs/scenes need `BehaviorParameters` + trained `.onnx` in `Model` field; verify with `list_prefabs_with_component` for `BehaviorParameters` if serialized as component name.
- **Sensors/Observations**: Stack vector observations consistently between training and runtime.
- **Performance**: Prefer inference on worker thread; cap decision frequency for mobile.
## Common prompts
- *"Does this project use ML-Agents?"*
- *"List ML-Agents training configs and ONNX models"*
- *"Which scripts reference BehaviorParameters?"*
---
name: unity-navmesh-npc
description: >-
Use for NavMesh, AI Navigation package, NavMeshAgent, NPC pathfinding,
off-mesh links, or gameplay AI movement (non-ML).
---
# Unity NavMesh & NPC AI
## Discovery
1. `get_navigation_settings` — agent radius, height, areas from ProjectSettings
2. `get_ai_ml_package_inventory` — `com.unity.ai.navigation` vs legacy built-in
3. `find_ai_related_scripts` — label **NavMesh / AI navigation**
4. `list_prefabs_with_component` with `NavMeshAgent`
5. `get_scene_components_by_type` with `NavMeshAgent` per scene
## Implementation checklist
- Bake NavMesh for each playable scene; store `NavMeshData` assets in version control.
- Tune agent avoidance priority and obstacle carving for crowds.
- Off-mesh links for jumps/doors — grep scenes for `OffMeshLink` usage via script scan.
- Separate **locomotion** (NavMeshAgent) from **decision** (state machine / behavior tree).
## Related tools
- `get_tags_and_layers` — AI layers vs player/environment collision
- `get_layer_collision_matrix` — agent vs projectile layers
---
name: unity-sentis-inference
description: >-
Use when integrating ONNX/Sentis/Unity Inference in Unity: model assets,
IWorker, runtime performance, or com.unity.sentis packages.
---
# Unity Sentis / ONNX inference
## Discovery
1. `list_ml_model_assets` — `.onnx`, `.nn`, `.pt` under Assets
2. `get_ai_ml_package_inventory` — Sentis, Barracuda, or `com.unity.ai.inference`
3. `find_ai_related_scripts` — labels **Sentis / InferenceEngine** or **ONNX**
## Implementation checklist
- Import ONNX to `ModelAsset`; use `WorkerFactory` / `IWorker` pattern for Sentis 2.x.
- Match input tensor names/shapes to training export metadata.
- Test on target platform (GPU vs CPU backend); mobile often needs quantized models.
- Barracuda is legacy — prefer Sentis for new work unless project is locked to Barracuda.
## Tools for impact analysis
- `find_references` on model asset path — who loads this ONNX?
- `get_prefab_dependencies` — prefabs pulling large models into builds
- `get_build_size_estimate` — ONNX size in player build