Skip to content

Commit 7997fab

Browse files
TMHSDigitalclaude
andcommitted
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MpEiWjv38TrdFcgqU8ER5L
1 parent 024b8fa commit 7997fab

11 files changed

Lines changed: 600 additions & 43 deletions

File tree

‎.cursor/mcp.json‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22
"mcpServers": {
33
"unity-dev-tools": {
44
"command": "python",
5-
"args": ["mcp-server/server.py"],
6-
"env": {
7-
"UNITY_DATA_PATH": "./mcp-server/data"
8-
}
5+
"args": ["mcp-server/server.py"]
96
}
107
}
118
}

‎.github/dependabot.yml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@ updates:
44
directory: "/"
55
schedule:
66
interval: "weekly"
7+
- package-ecosystem: "pip"
8+
directory: "/mcp-server"
9+
schedule:
10+
interval: "weekly"

‎.github/workflows/validate.yml‎

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,18 @@ jobs:
245245
fi
246246
echo "No em/en dashes found"
247247
248+
- name: Check C# stays within Unity 6 language version (C# 9)
249+
run: |
250+
if grep -rnP '^\s*namespace\s+[A-Za-z0-9_.]+\s*;' --include='*.cs' . 2>/dev/null; then
251+
echo "::error::File-scoped namespaces are C# 10 and do not compile in Unity 6. Use block-scoped namespaces."
252+
exit 1
253+
fi
254+
if grep -rniP '\b(use|prefer)\s+(file-scoped namespaces|primary constructors)' --include='*.md' --include='*.mdc' skills rules snippets templates 2>/dev/null; then
255+
echo "::error::Guidance recommends C# 10+ features that Unity 6 cannot compile."
256+
exit 1
257+
fi
258+
echo "No C# 10+ constructs or recommendations found"
259+
248260
- name: Check for hardcoded credentials
249261
run: |
250262
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:
367379
PYEOF
368380
369381
validate-python:
370-
name: Validate MCP server
382+
name: Validate MCP server (Python ${{ matrix.python-version }})
371383
runs-on: ubuntu-latest
384+
strategy:
385+
fail-fast: false
386+
matrix:
387+
python-version: ["3.10", "3.12"]
372388
steps:
373389
- uses: actions/checkout@v6
374390

375391
- uses: actions/setup-python@v6
376392
with:
377-
python-version: "3.12"
393+
python-version: ${{ matrix.python-version }}
378394

379395
- name: Install dependencies
380-
run: pip install -r mcp-server/requirements.txt
396+
run: pip install -r mcp-server/requirements-dev.txt
381397

382398
- name: Check Python syntax
383399
run: |
384-
python3 -m py_compile mcp-server/server.py
400+
python -m py_compile mcp-server/server.py
385401
for f in mcp-server/tools/*.py; do
386-
python3 -m py_compile "$f"
402+
python -m py_compile "$f"
387403
done
388404
echo "All Python files pass syntax check"
405+
406+
- name: Run MCP server tests
407+
run: python -m pytest mcp-server/tests -v

‎mcp-server/data/deprecated_patterns.json‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,5 +100,23 @@
100100
"replacement": "JSON serialization to Application.persistentDataPath or a save system",
101101
"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.",
102102
"since_version": "N/A (best practice)"
103+
},
104+
{
105+
"legacy": "Rigidbody.velocity / Rigidbody2D.velocity",
106+
"replacement": "Rigidbody.linearVelocity / Rigidbody2D.linearVelocity",
107+
"reason": "Renamed in Unity 6 to distinguish linear from angular velocity. The old property is obsolete.",
108+
"since_version": "6000.0"
109+
},
110+
{
111+
"legacy": "Rigidbody.drag / Rigidbody.angularDrag",
112+
"replacement": "Rigidbody.linearDamping / Rigidbody.angularDamping",
113+
"reason": "Renamed in Unity 6 to match physics terminology. The old properties are obsolete.",
114+
"since_version": "6000.0"
115+
},
116+
{
117+
"legacy": "[ServerRpc] / [ClientRpc]",
118+
"replacement": "[Rpc(SendTo.Server)] / [Rpc(SendTo.ClientsAndHost)]",
119+
"reason": "Netcode for GameObjects universal RPCs replace the separate ServerRpc and ClientRpc attributes and support more send targets.",
120+
"since_version": "NGO 1.8"
103121
}
104122
]

‎mcp-server/data/shader_properties.json‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"effect": "Dissolve",
44
"description": "Dissolves an object using a noise texture, revealing or hiding geometry along a threshold. Commonly used for death effects, teleportation, and material transitions.",
55
"pipelines": ["urp", "hdrp", "builtin"],
6+
"code_pipeline": "urp",
67
"properties": [
78
{"name": "_DissolveAmount", "type": "Range(0,1)", "description": "Controls how much of the object is dissolved. 0 = fully visible, 1 = fully dissolved."},
89
{"name": "_NoiseTex", "type": "2D", "description": "Grayscale noise texture that drives the dissolve pattern."},
@@ -22,6 +23,7 @@
2223
"effect": "Outline",
2324
"description": "Renders a visible outline around objects. Used for selection highlights, toon rendering, and interactable object indication.",
2425
"pipelines": ["urp", "hdrp", "builtin"],
26+
"code_pipeline": "urp",
2527
"properties": [
2628
{"name": "_OutlineColor", "type": "Color", "description": "Color of the outline."},
2729
{"name": "_OutlineWidth", "type": "Float", "description": "Thickness of the outline in clip space or world units."},
@@ -40,6 +42,7 @@
4042
"effect": "Fresnel",
4143
"description": "Brightens or colors the edges of an object based on view angle. Used for shields, holographic effects, and rim lighting.",
4244
"pipelines": ["urp", "hdrp", "builtin"],
45+
"code_pipeline": "urp",
4346
"properties": [
4447
{"name": "_FresnelColor", "type": "Color", "description": "Color applied to the fresnel rim effect."},
4548
{"name": "_FresnelPower", "type": "Float", "description": "Controls the falloff of the fresnel effect. Higher values create a tighter rim."},
@@ -58,6 +61,7 @@
5861
"effect": "Toon",
5962
"description": "Cel-shading effect that quantizes lighting into discrete bands for a cartoon or anime art style.",
6063
"pipelines": ["urp", "hdrp", "builtin"],
64+
"code_pipeline": "urp",
6165
"properties": [
6266
{"name": "_BaseColor", "type": "Color", "description": "Main tint color of the surface."},
6367
{"name": "_ShadowColor", "type": "Color", "description": "Color used for shadowed areas instead of darkening."},
@@ -78,6 +82,7 @@
7882
"effect": "Water",
7983
"description": "Stylized or semi-realistic water surface with scrolling normals, depth-based coloring, and vertex displacement.",
8084
"pipelines": ["urp", "hdrp", "builtin"],
85+
"code_pipeline": "urp",
8186
"properties": [
8287
{"name": "_ShallowColor", "type": "Color", "description": "Water color in shallow areas near the shore."},
8388
{"name": "_DeepColor", "type": "Color", "description": "Water color in deep areas away from the shore."},
@@ -100,6 +105,7 @@
100105
"effect": "Hologram",
101106
"description": "Sci-fi holographic projection effect with scan lines, flickering, and transparent rendering.",
102107
"pipelines": ["urp", "hdrp", "builtin"],
108+
"code_pipeline": "urp",
103109
"properties": [
104110
{"name": "_HoloColor", "type": "Color", "description": "Primary color of the hologram."},
105111
{"name": "_ScanLineSpeed", "type": "Float", "description": "Speed at which horizontal scan lines scroll."},

‎mcp-server/data/unity_api_common.json‎

Lines changed: 132 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,20 @@
128128
"example": "rb.MovePosition(rb.position + velocity * Time.fixedDeltaTime);"
129129
},
130130
{
131-
"name": "Rigidbody.velocity",
131+
"name": "Rigidbody.linearVelocity",
132132
"namespace": "UnityEngine",
133133
"category": "physics",
134-
"description": "The velocity vector of the Rigidbody in world space. Setting directly bypasses physics simulation - prefer AddForce for realistic movement.",
135-
"signature": "Vector3 Rigidbody.velocity { get; set; }",
136-
"example": "rb.velocity = new Vector3(rb.velocity.x, jumpSpeed, rb.velocity.z);"
134+
"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.",
135+
"signature": "Vector3 Rigidbody.linearVelocity { get; set; }",
136+
"example": "rb.linearVelocity = new Vector3(rb.linearVelocity.x, jumpSpeed, rb.linearVelocity.z);"
137+
},
138+
{
139+
"name": "Rigidbody.linearDamping",
140+
"namespace": "UnityEngine",
141+
"category": "physics",
142+
"description": "Linear drag applied to the Rigidbody (renamed from Rigidbody.drag in Unity 6). angularDamping replaces angularDrag.",
143+
"signature": "float Rigidbody.linearDamping { get; set; }",
144+
"example": "rb.linearDamping = 0.5f;"
137145
},
138146
{
139147
"name": "Rigidbody2D.AddForce",
@@ -470,5 +478,125 @@
470478
"description": "Finds the first loaded object of the given type with deterministic ordering by instance ID. Unity 2023.1+.",
471479
"signature": "static T Object.FindFirstObjectByType<T>(FindObjectsInactive findObjectsInactive = FindObjectsInactive.Exclude)",
472480
"example": "var player = FindFirstObjectByType<PlayerController>();"
481+
},
482+
{
483+
"name": "InputSystem.actions",
484+
"namespace": "UnityEngine.InputSystem",
485+
"category": "input",
486+
"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.",
487+
"signature": "static InputActionAsset InputSystem.actions { get; set; }",
488+
"example": "var move = InputSystem.actions.FindAction(\"Move\");"
489+
},
490+
{
491+
"name": "InputActionAsset.FindAction",
492+
"namespace": "UnityEngine.InputSystem",
493+
"category": "input",
494+
"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.",
495+
"signature": "InputAction InputActionAsset.FindAction(string actionNameOrId, bool throwIfNotFound = false)",
496+
"example": "_jump = InputSystem.actions.FindAction(\"Player/Jump\", throwIfNotFound: true);"
497+
},
498+
{
499+
"name": "InputAction.ReadValue",
500+
"namespace": "UnityEngine.InputSystem",
501+
"category": "input",
502+
"description": "Reads the current value of the action, for example a Vector2 for a move stick or composite.",
503+
"signature": "TValue InputAction.ReadValue<TValue>() where TValue : struct",
504+
"example": "Vector2 move = _move.ReadValue<Vector2>();"
505+
},
506+
{
507+
"name": "InputAction.WasPressedThisFrame",
508+
"namespace": "UnityEngine.InputSystem",
509+
"category": "input",
510+
"description": "Returns true if the action was pressed during the current frame. Replaces Input.GetButtonDown polling.",
511+
"signature": "bool InputAction.WasPressedThisFrame()",
512+
"example": "if (_jump.WasPressedThisFrame()) Jump();"
513+
},
514+
{
515+
"name": "UIDocument.rootVisualElement",
516+
"namespace": "UnityEngine.UIElements",
517+
"category": "ui",
518+
"description": "The root VisualElement of a UI Toolkit document. Query it in OnEnable, since the tree is rebuilt when the UIDocument is enabled.",
519+
"signature": "VisualElement UIDocument.rootVisualElement { get; }",
520+
"example": "var root = GetComponent<UIDocument>().rootVisualElement;"
521+
},
522+
{
523+
"name": "UQueryExtensions.Q",
524+
"namespace": "UnityEngine.UIElements",
525+
"category": "ui",
526+
"description": "Returns the first descendant element matching the type, name, and/or USS class.",
527+
"signature": "static T UQueryExtensions.Q<T>(this VisualElement e, string name = null, string className = null) where T : VisualElement",
528+
"example": "var playButton = root.Q<Button>(\"play-button\");"
529+
},
530+
{
531+
"name": "Button.clicked",
532+
"namespace": "UnityEngine.UIElements",
533+
"category": "ui",
534+
"description": "Event raised when a UI Toolkit Button is clicked. Unsubscribe in OnDisable to avoid duplicate handlers.",
535+
"signature": "event Action Button.clicked",
536+
"example": "playButton.clicked += OnPlayClicked;"
537+
},
538+
{
539+
"name": "VisualElement.SetBinding",
540+
"namespace": "UnityEngine.UIElements",
541+
"category": "ui",
542+
"description": "Runtime data binding (Unity 6): binds an element property to a path on its dataSource.",
543+
"signature": "void VisualElement.SetBinding(BindingId bindingId, Binding binding)",
544+
"example": "label.dataSource = playerData; label.SetBinding(\"text\", new DataBinding { dataSourcePath = new PropertyPath(nameof(PlayerData.Name)) });"
545+
},
546+
{
547+
"name": "NetworkManager.StartHost",
548+
"namespace": "Unity.Netcode",
549+
"category": "networking",
550+
"description": "Starts a server and a local client (Netcode for GameObjects). Use StartServer or StartClient for dedicated roles.",
551+
"signature": "bool NetworkManager.StartHost()",
552+
"example": "NetworkManager.Singleton.StartHost();"
553+
},
554+
{
555+
"name": "NetworkVariable",
556+
"namespace": "Unity.Netcode",
557+
"category": "networking",
558+
"description": "A value synchronized from the server (or owner) to clients. Subscribe to OnValueChanged in OnNetworkSpawn.",
559+
"signature": "NetworkVariable<T>(T value = default, NetworkVariableReadPermission readPerm = NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission writePerm = NetworkVariableWritePermission.Server)",
560+
"example": "private readonly NetworkVariable<int> _health = new(100);"
561+
},
562+
{
563+
"name": "RpcAttribute",
564+
"namespace": "Unity.Netcode",
565+
"category": "networking",
566+
"description": "Universal RPC attribute (Netcode for GameObjects 1.8+ and 2.x) that replaces [ServerRpc] and [ClientRpc]. The method name must end in Rpc.",
567+
"signature": "[Rpc(SendTo target)]",
568+
"example": "[Rpc(SendTo.Server)] private void FireRpc(Vector3 direction) { /* runs on server */ }"
569+
},
570+
{
571+
"name": "EditorWindow.GetWindow",
572+
"namespace": "UnityEditor",
573+
"category": "editor",
574+
"description": "Returns the first open EditorWindow of type T, creating and showing one if none exists.",
575+
"signature": "static T EditorWindow.GetWindow<T>(string title) where T : EditorWindow",
576+
"example": "[MenuItem(\"Tools/Level Builder\")] static void Open() => GetWindow<LevelBuilderWindow>(\"Level Builder\");"
577+
},
578+
{
579+
"name": "Editor.CreateInspectorGUI",
580+
"namespace": "UnityEditor",
581+
"category": "editor",
582+
"description": "Override to build a custom inspector with UI Toolkit. Return a VisualElement; use InspectorElement.FillDefaultInspector to include the default fields.",
583+
"signature": "virtual VisualElement Editor.CreateInspectorGUI()",
584+
"example": "public override VisualElement CreateInspectorGUI() { var root = new VisualElement(); InspectorElement.FillDefaultInspector(root, serializedObject, this); return root; }"
585+
},
586+
{
587+
"name": "PrefabUtility.InstantiatePrefab",
588+
"namespace": "UnityEditor",
589+
"category": "editor",
590+
"description": "Instantiates a prefab asset in the scene while keeping the prefab connection. Returns null if passed a scene object instead of an asset.",
591+
"signature": "static Object PrefabUtility.InstantiatePrefab(Object assetComponentOrGameObject)",
592+
"example": "var go = (GameObject)PrefabUtility.InstantiatePrefab(prefab); Undo.RegisterCreatedObjectUndo(go, \"Place Prefab\");"
593+
},
594+
{
595+
"name": "Undo.RegisterCreatedObjectUndo",
596+
"namespace": "UnityEditor",
597+
"category": "editor",
598+
"description": "Registers a newly created object so the creation can be undone with Ctrl+Z.",
599+
"signature": "static void Undo.RegisterCreatedObjectUndo(Object objectToUndo, string name)",
600+
"example": "Undo.RegisterCreatedObjectUndo(go, \"Create Enemy\");"
473601
}
474602
]

‎mcp-server/requirements-dev.txt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-r requirements.txt
2+
pytest>=8,<9

‎mcp-server/requirements.txt‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
1-
mcp>=1.0.0
2-
fastapi>=0.100.0
3-
uvicorn>=0.23.0
1+
mcp>=1.2.0,<2

0 commit comments

Comments
 (0)