diff --git a/.cursor/mcp.json b/.cursor/mcp.json index d2dc65b..931b6ff 100644 --- a/.cursor/mcp.json +++ b/.cursor/mcp.json @@ -2,10 +2,7 @@ "mcpServers": { "unity-dev-tools": { "command": "python", - "args": ["mcp-server/server.py"], - "env": { - "UNITY_DATA_PATH": "./mcp-server/data" - } + "args": ["mcp-server/server.py"] } } } diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5ace460..82f07b3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,3 +4,7 @@ updates: directory: "/" schedule: interval: "weekly" + - package-ecosystem: "pip" + directory: "/mcp-server" + schedule: + interval: "weekly" diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 8bb6da6..da6b718 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -245,6 +245,18 @@ jobs: fi echo "No em/en dashes found" + - name: Check C# stays within Unity 6 language version (C# 9) + run: | + if grep -rnP '^\s*namespace\s+[A-Za-z0-9_.]+\s*;' --include='*.cs' . 2>/dev/null; then + echo "::error::File-scoped namespaces are C# 10 and do not compile in Unity 6. Use block-scoped namespaces." + exit 1 + fi + if grep -rniP '\b(use|prefer)\s+(file-scoped namespaces|primary constructors)' --include='*.md' --include='*.mdc' skills rules snippets templates 2>/dev/null; then + echo "::error::Guidance recommends C# 10+ features that Unity 6 cannot compile." + exit 1 + fi + echo "No C# 10+ constructs or recommendations found" + - name: Check for hardcoded credentials run: | patterns='password\s*=\s*["\x27][^"\x27]+|api_key\s*=\s*["\x27][^"\x27]+|token\s*=\s*["\x27][A-Za-z0-9]+' @@ -367,22 +379,29 @@ jobs: PYEOF validate-python: - name: Validate MCP server + name: Validate MCP server (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.12"] steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: "3.12" + python-version: ${{ matrix.python-version }} - name: Install dependencies - run: pip install -r mcp-server/requirements.txt + run: pip install -r mcp-server/requirements-dev.txt - name: Check Python syntax run: | - python3 -m py_compile mcp-server/server.py + python -m py_compile mcp-server/server.py for f in mcp-server/tools/*.py; do - python3 -m py_compile "$f" + python -m py_compile "$f" done echo "All Python files pass syntax check" + + - name: Run MCP server tests + run: python -m pytest mcp-server/tests -v diff --git a/README.md b/README.md index 4e70241..e4c9c64 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Then ask the AI agent to scaffold a MonoBehaviour, look up an API, or generate a ## Features -- **Script scaffolding** -- Generate MonoBehaviours, ScriptableObjects, Editor windows, and ECS systems following Unity 6 conventions +- **Script scaffolding** -- Generate MonoBehaviours, ScriptableObjects, Editor windows, inspectors, state machines, and tests following Unity 6 conventions - **API lookup** -- Search common Unity APIs by name, namespace, or category via MCP tools - **Shader patterns** -- Get HLSL code and Shader Graph node setups for common effects (dissolve, outline, hologram, etc.) - **Performance-aware coding rules** -- Catch deprecated APIs, allocation-heavy patterns, and common mistakes @@ -243,7 +243,7 @@ The server starts automatically when Cursor invokes an MCP tool. | Tool | Description | |:-----|:------------| -| `scaffold_script` | Generate C# scripts following Unity 6 conventions. Supports MonoBehaviour, ScriptableObject, Editor, and ECS templates. | +| `scaffold_script` | Generate C# scripts following Unity 6 conventions. Supports MonoBehaviour, ScriptableObject, Editor window, custom inspector, property drawer, interface, state machine, and test templates. | | `lookup_api` | Search the Unity API reference database by name, namespace, or category. Returns signatures, descriptions, and examples. | | `shader_helper` | Get shader code patterns for common effects (dissolve, outline, hologram, etc.) with HLSL and Shader Graph guidance. | | `platform_info` | Get platform-specific defines, capabilities, limitations, and build recommendations. | diff --git a/docs/index.md b/docs/index.md index 0fb14ca..98ebe9c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,7 +10,7 @@ Unity Developer Tools is a plugin for [Cursor](https://www.cursor.com/) that teaches its AI assistant how to build Unity games and tools. Once installed, you can ask the AI to: -- Scaffold MonoBehaviours, ScriptableObjects, Editor windows, and ECS systems +- Scaffold MonoBehaviours, ScriptableObjects, Editor windows, inspectors, and tests - Look up Unity APIs by name, namespace, or category - Generate shader patterns for common effects (dissolve, outline, hologram, etc.) - Get platform-specific defines, capabilities, and build recommendations @@ -29,7 +29,7 @@ For a detailed walkthrough, see the [Getting Started guide](GETTING-STARTED.md). ## Features -- **Script scaffolding** -- Generate MonoBehaviours, ScriptableObjects, Editor windows, and ECS systems following Unity 6 conventions +- **Script scaffolding** -- Generate MonoBehaviours, ScriptableObjects, Editor windows, inspectors, state machines, and tests following Unity 6 conventions - **API lookup** -- Search common Unity APIs by name, namespace, or category via MCP tools - **Shader patterns** -- Get HLSL code and Shader Graph node setups for common effects - **Performance-aware coding rules** -- Catch deprecated APIs, allocation-heavy patterns, and common mistakes diff --git a/mcp-server/README.md b/mcp-server/README.md index 7be0ece..8c64f38 100644 --- a/mcp-server/README.md +++ b/mcp-server/README.md @@ -7,16 +7,17 @@ A Model Context Protocol server providing Unity development tools for the Cursor ### scaffold_script Generate well-structured C# scripts following Unity conventions. - **type**: monobehaviour, scriptableobject, editor-window, custom-inspector, property-drawer, interface, state-machine, test -- **name**: Class name for the generated script +- **name**: Class name for the generated script (must be a valid C# identifier) - **namespace**: Optional namespace (default: MyGame) +- **target_type**: Type inspected or drawn, for custom-inspector and property-drawer. Defaults to the name without its Editor/Inspector/Drawer suffix (`PlayerEditor` -> `Player`) ### lookup_api -Search the Unity API reference database for classes, methods, and usage patterns. +Search the Unity API reference database for classes, methods, and usage patterns. Multi-word queries match entries containing every word; exact name matches rank first. - **query**: Search term (class name, method name, or keyword) -- **category**: Optional filter (physics, ui, animation, audio, rendering, input, networking, editor) +- **category**: Optional filter (general, physics, ui, animation, audio, rendering, input, networking, editor) ### shader_helper -Get shader code patterns and property setups for common effects. +Get shader code patterns and property setups for common effects. Hand-written HLSL currently targets URP; HDRP and Built-in requests return properties and Shader Graph guidance only. - **effect**: Effect name (dissolve, outline, toon, water, hologram, fresnel) - **pipeline**: Target render pipeline (urp, hdrp, builtin) @@ -34,9 +35,20 @@ pip install -r requirements.txt python server.py ``` +The server reads its data from `data/` next to `server.py`, so it can be started from any directory. Set `UNITY_DATA_PATH` to use a different data folder. If a data file is missing, the server exits with an error instead of returning empty results. + +## Testing + +```bash +pip install -r requirements-dev.txt +python -m pytest tests +``` + +The suite covers every tool and includes a stdio smoke test that starts the server and calls it through the MCP client. + ## Data Files -- `unity_api_common.json` - Common Unity API reference (200+ classes and methods) +- `unity_api_common.json` - Curated Unity 6 API reference (75 entries across 9 categories) - `shader_properties.json` - Built-in shader properties and effect patterns - `platform_defines.json` - Platform scripting defines and capabilities - `lifecycle_order.json` - MonoBehaviour execution order reference diff --git a/mcp-server/data/deprecated_patterns.json b/mcp-server/data/deprecated_patterns.json index ad8fa1b..04316fa 100644 --- a/mcp-server/data/deprecated_patterns.json +++ b/mcp-server/data/deprecated_patterns.json @@ -100,5 +100,23 @@ "replacement": "JSON serialization to Application.persistentDataPath or a save system", "reason": "PlayerPrefs is stored in the registry on Windows and has size limits. It is not encrypted, not portable, and not suitable for structured save data.", "since_version": "N/A (best practice)" + }, + { + "legacy": "Rigidbody.velocity / Rigidbody2D.velocity", + "replacement": "Rigidbody.linearVelocity / Rigidbody2D.linearVelocity", + "reason": "Renamed in Unity 6 to distinguish linear from angular velocity. The old property is obsolete.", + "since_version": "6000.0" + }, + { + "legacy": "Rigidbody.drag / Rigidbody.angularDrag", + "replacement": "Rigidbody.linearDamping / Rigidbody.angularDamping", + "reason": "Renamed in Unity 6 to match physics terminology. The old properties are obsolete.", + "since_version": "6000.0" + }, + { + "legacy": "[ServerRpc] / [ClientRpc]", + "replacement": "[Rpc(SendTo.Server)] / [Rpc(SendTo.ClientsAndHost)]", + "reason": "Netcode for GameObjects universal RPCs replace the separate ServerRpc and ClientRpc attributes and support more send targets.", + "since_version": "NGO 1.8" } ] diff --git a/mcp-server/data/shader_properties.json b/mcp-server/data/shader_properties.json index 3509005..81d1850 100644 --- a/mcp-server/data/shader_properties.json +++ b/mcp-server/data/shader_properties.json @@ -3,6 +3,7 @@ "effect": "Dissolve", "description": "Dissolves an object using a noise texture, revealing or hiding geometry along a threshold. Commonly used for death effects, teleportation, and material transitions.", "pipelines": ["urp", "hdrp", "builtin"], + "code_pipeline": "urp", "properties": [ {"name": "_DissolveAmount", "type": "Range(0,1)", "description": "Controls how much of the object is dissolved. 0 = fully visible, 1 = fully dissolved."}, {"name": "_NoiseTex", "type": "2D", "description": "Grayscale noise texture that drives the dissolve pattern."}, @@ -22,6 +23,7 @@ "effect": "Outline", "description": "Renders a visible outline around objects. Used for selection highlights, toon rendering, and interactable object indication.", "pipelines": ["urp", "hdrp", "builtin"], + "code_pipeline": "urp", "properties": [ {"name": "_OutlineColor", "type": "Color", "description": "Color of the outline."}, {"name": "_OutlineWidth", "type": "Float", "description": "Thickness of the outline in clip space or world units."}, @@ -40,6 +42,7 @@ "effect": "Fresnel", "description": "Brightens or colors the edges of an object based on view angle. Used for shields, holographic effects, and rim lighting.", "pipelines": ["urp", "hdrp", "builtin"], + "code_pipeline": "urp", "properties": [ {"name": "_FresnelColor", "type": "Color", "description": "Color applied to the fresnel rim effect."}, {"name": "_FresnelPower", "type": "Float", "description": "Controls the falloff of the fresnel effect. Higher values create a tighter rim."}, @@ -58,6 +61,7 @@ "effect": "Toon", "description": "Cel-shading effect that quantizes lighting into discrete bands for a cartoon or anime art style.", "pipelines": ["urp", "hdrp", "builtin"], + "code_pipeline": "urp", "properties": [ {"name": "_BaseColor", "type": "Color", "description": "Main tint color of the surface."}, {"name": "_ShadowColor", "type": "Color", "description": "Color used for shadowed areas instead of darkening."}, @@ -78,6 +82,7 @@ "effect": "Water", "description": "Stylized or semi-realistic water surface with scrolling normals, depth-based coloring, and vertex displacement.", "pipelines": ["urp", "hdrp", "builtin"], + "code_pipeline": "urp", "properties": [ {"name": "_ShallowColor", "type": "Color", "description": "Water color in shallow areas near the shore."}, {"name": "_DeepColor", "type": "Color", "description": "Water color in deep areas away from the shore."}, @@ -100,6 +105,7 @@ "effect": "Hologram", "description": "Sci-fi holographic projection effect with scan lines, flickering, and transparent rendering.", "pipelines": ["urp", "hdrp", "builtin"], + "code_pipeline": "urp", "properties": [ {"name": "_HoloColor", "type": "Color", "description": "Primary color of the hologram."}, {"name": "_ScanLineSpeed", "type": "Float", "description": "Speed at which horizontal scan lines scroll."}, diff --git a/mcp-server/data/unity_api_common.json b/mcp-server/data/unity_api_common.json index e1a7833..cd77b32 100644 --- a/mcp-server/data/unity_api_common.json +++ b/mcp-server/data/unity_api_common.json @@ -128,12 +128,20 @@ "example": "rb.MovePosition(rb.position + velocity * Time.fixedDeltaTime);" }, { - "name": "Rigidbody.velocity", + "name": "Rigidbody.linearVelocity", "namespace": "UnityEngine", "category": "physics", - "description": "The velocity vector of the Rigidbody in world space. Setting directly bypasses physics simulation - prefer AddForce for realistic movement.", - "signature": "Vector3 Rigidbody.velocity { get; set; }", - "example": "rb.velocity = new Vector3(rb.velocity.x, jumpSpeed, rb.velocity.z);" + "description": "The linear velocity vector of the Rigidbody in world space (renamed from Rigidbody.velocity in Unity 6). Setting directly bypasses physics simulation - prefer AddForce for realistic movement.", + "signature": "Vector3 Rigidbody.linearVelocity { get; set; }", + "example": "rb.linearVelocity = new Vector3(rb.linearVelocity.x, jumpSpeed, rb.linearVelocity.z);" + }, + { + "name": "Rigidbody.linearDamping", + "namespace": "UnityEngine", + "category": "physics", + "description": "Linear drag applied to the Rigidbody (renamed from Rigidbody.drag in Unity 6). angularDamping replaces angularDrag.", + "signature": "float Rigidbody.linearDamping { get; set; }", + "example": "rb.linearDamping = 0.5f;" }, { "name": "Rigidbody2D.AddForce", @@ -470,5 +478,125 @@ "description": "Finds the first loaded object of the given type with deterministic ordering by instance ID. Unity 2023.1+.", "signature": "static T Object.FindFirstObjectByType(FindObjectsInactive findObjectsInactive = FindObjectsInactive.Exclude)", "example": "var player = FindFirstObjectByType();" + }, + { + "name": "InputSystem.actions", + "namespace": "UnityEngine.InputSystem", + "category": "input", + "description": "The project-wide Input Action Asset (Unity 6). Configure actions in Project Settings > Input System Package and look them up by name at runtime.", + "signature": "static InputActionAsset InputSystem.actions { get; set; }", + "example": "var move = InputSystem.actions.FindAction(\"Move\");" + }, + { + "name": "InputActionAsset.FindAction", + "namespace": "UnityEngine.InputSystem", + "category": "input", + "description": "Finds an action by name, \"Map/Action\" path, or ID. Cache the result in Awake or Start instead of looking it up every frame.", + "signature": "InputAction InputActionAsset.FindAction(string actionNameOrId, bool throwIfNotFound = false)", + "example": "_jump = InputSystem.actions.FindAction(\"Player/Jump\", throwIfNotFound: true);" + }, + { + "name": "InputAction.ReadValue", + "namespace": "UnityEngine.InputSystem", + "category": "input", + "description": "Reads the current value of the action, for example a Vector2 for a move stick or composite.", + "signature": "TValue InputAction.ReadValue() where TValue : struct", + "example": "Vector2 move = _move.ReadValue();" + }, + { + "name": "InputAction.WasPressedThisFrame", + "namespace": "UnityEngine.InputSystem", + "category": "input", + "description": "Returns true if the action was pressed during the current frame. Replaces Input.GetButtonDown polling.", + "signature": "bool InputAction.WasPressedThisFrame()", + "example": "if (_jump.WasPressedThisFrame()) Jump();" + }, + { + "name": "UIDocument.rootVisualElement", + "namespace": "UnityEngine.UIElements", + "category": "ui", + "description": "The root VisualElement of a UI Toolkit document. Query it in OnEnable, since the tree is rebuilt when the UIDocument is enabled.", + "signature": "VisualElement UIDocument.rootVisualElement { get; }", + "example": "var root = GetComponent().rootVisualElement;" + }, + { + "name": "UQueryExtensions.Q", + "namespace": "UnityEngine.UIElements", + "category": "ui", + "description": "Returns the first descendant element matching the type, name, and/or USS class.", + "signature": "static T UQueryExtensions.Q(this VisualElement e, string name = null, string className = null) where T : VisualElement", + "example": "var playButton = root.Q