From 7997fabbf860ae2bd29d516661a4a699f572afaf Mon Sep 17 00:00:00 2001 From: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:04:21 -0400 Subject: [PATCH 1/3] fix: make MCP server tools correct and testable - Resolve data files next to server.py so tools work from any working directory, and fail at startup instead of returning empty results (#22) - shader_helper no longer labels URP-only HLSL as HDRP or Built-in code; validates pipeline and lists available effects (#24) - scaffold_script validates C# identifiers, replaces the TARGET_TYPE placeholder with an inferred or explicit target_type, gives each state machine its own state interface, and notes Editor/test assembly needs (#25) - lookup_api: multi-word ranked search, category validation, Unity 6 linearVelocity/linearDamping entries, and new input, ui, networking and editor entries so no advertised category is empty (#26) - Add pytest suite with a stdio MCP smoke test, run it in CI on Python 3.10 and 3.12, drop unused fastapi/uvicorn, bound mcp to <2, and add pip to Dependabot (#36) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MpEiWjv38TrdFcgqU8ER5L --- .cursor/mcp.json | 5 +- .github/dependabot.yml | 4 + .github/workflows/validate.yml | 29 ++- mcp-server/data/deprecated_patterns.json | 18 ++ mcp-server/data/shader_properties.json | 6 + mcp-server/data/unity_api_common.json | 136 ++++++++++++- mcp-server/requirements-dev.txt | 2 + mcp-server/requirements.txt | 4 +- mcp-server/server.py | 201 ++++++++++++++++--- mcp-server/tests/conftest.py | 5 + mcp-server/tests/test_server.py | 233 +++++++++++++++++++++++ 11 files changed, 600 insertions(+), 43 deletions(-) create mode 100644 mcp-server/requirements-dev.txt create mode 100644 mcp-server/tests/conftest.py create mode 100644 mcp-server/tests/test_server.py 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/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