unity-mcp-server
Advanced tools
| /** | ||
| * 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: ... | ||
| ``` |
+111
| # 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. |
+50
-8
@@ -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); |
| /** | ||
| * 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 @@ } |
+74
-24
@@ -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; | ||
| } | ||
| } |
+61
-27
| /** | ||
| * 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(".")) |
+16
-9
| /** | ||
| * 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"); |
+13
-6
| /** | ||
| * 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 @@ } |
+178
-38
| /** | ||
| * 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) { |
+2
-2
| { | ||
| "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", |
+24
-6
@@ -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) |
+3
-3
@@ -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 |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
67
103.03%4231
17.23%455
4.12%278054
-94.32%