Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ jobs:
- name: Upload coverage reports
uses: codecov/codecov-action@v4
if: always()
# fail_ci_if_error: false does not cover a failed download: v4 crashes on the https
# error before it reads that input. This job gates both release pipelines, and a
# Codecov outage must not stop a release.
continue-on-error: true
with:
files: ./Server/coverage.xml
flags: python
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,19 @@ private static void ProcessCommand(string id, PendingCommand pending)

var parameters = command.@params ?? new JObject();

// An unknown command is the caller's mistake or a version mismatch (a server or
// CLI newer than this package), not an Editor fault: answer it without logging
// an error and a stack trace to the user's console.
if (!CommandRegistry.HasHandler(command.type))
{
string unknown = $"Unity has no tool or resource named '{command.type}'. If the MCP server " +
"or CLI is newer than the MCP for Unity package in this project, update the package.";
McpLog.Warn(unknown);
pending.TrySetResult(SerializeError(unknown, command.type));
RemovePending(id, pending);
return;
}

// Block execution of disabled resources
var resourceMeta = MCPServiceLocator.ResourceDiscovery.GetResourceMetadata(command.type);
if (resourceMeta != null && !MCPServiceLocator.ResourceDiscovery.IsResourceEnabled(command.type))
Expand Down
5 changes: 5 additions & 0 deletions MCPForUnity/Editor/Tools/CommandRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ private static bool RegisterCommandType(Type type, bool isResource)
}
}

/// <summary>
/// True when a tool or resource handler is registered under this name.
/// </summary>
public static bool HasHandler(string commandName) => _handlers.ContainsKey(commandName);

/// <summary>
/// Get a command handler by name
/// </summary>
Expand Down
69 changes: 65 additions & 4 deletions Server/src/cli/CLI_USAGE_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -607,10 +607,15 @@ unity-mcp audio volume "MusicPlayer" 0.5

```bash
# Control Animator (target or one of its children must have an Animator component)
unity-mcp animation play "Character" "Walk"
unity-mcp animation set-parameter "Character" "Speed" 1.5 --type float
unity-mcp animation set-parameter "Character" "IsRunning" true --type bool
unity-mcp animation set-parameter "Character" "Jump" "" --type trigger
unity-mcp animation animator play "Character" "Walk"
unity-mcp animation animator set-parameter "Character" "Speed" 1.5 --type float
unity-mcp animation animator set-parameter "Character" "IsRunning" true --type bool
unity-mcp animation animator set-parameter "Character" "Jump" "" --type trigger

# Clips and controllers
unity-mcp animation clip create "Assets/Animations/Bounce.anim" --length 2.0 --loop
unity-mcp animation clip info "Assets/Animations/Walk.anim"
unity-mcp animation controller info "Assets/Animations/Player.controller"
```

### Camera Commands
Expand Down Expand Up @@ -815,6 +820,62 @@ unity-mcp code search "TODO|FIXME" "Assets/Scripts/Utils.cs"
unity-mcp code search "void Update" "Assets/Scripts/Game.cs" --max-results 20
```

### Build Commands

```bash
unity-mcp build platform # Read the active platform
unity-mcp build platform android # Switch platform
unity-mcp build scenes # Read the build scene list
unity-mcp build run --target windows64 --development
unity-mcp build batch --targets windows64,linux64,webgl
unity-mcp build status # Last build report
unity-mcp build settings product_name --value "My Game"
```

### Physics Commands

```bash
unity-mcp physics ping # Physics system status
unity-mcp physics get-settings
unity-mcp physics raycast --origin "0,5,0" --direction "0,-1,0" --max-distance 10
unity-mcp physics overlap --shape sphere --position "0,0,0" --size 2
unity-mcp physics get-rigidbody "Player"
unity-mcp physics simulate --steps 10 # Step physics in edit mode
unity-mcp physics validate # Check the scene for common mistakes
```

### Profiler Commands

```bash
unity-mcp profiler start # Optionally --log-file to record a .raw
unity-mcp profiler status
unity-mcp profiler frame-timing
unity-mcp profiler get-counters --category Render
unity-mcp profiler memory-snapshot # Requires com.unity.memoryprofiler
unity-mcp profiler stop
```

### Reflection and Docs Commands

```bash
unity-mcp reflect search NavMesh # Find Unity types by name
unity-mcp reflect type NavMeshAgent # Member summary
unity-mcp reflect member Physics Raycast # One member in detail
unity-mcp docs get Physics Raycast # Unity documentation page
```

### Asset Generation and Blender Commands

```bash
unity-mcp asset-gen list-models --kind image
unity-mcp asset-gen generate-image --provider fal --prompt "a stone texture"
unity-mcp asset-gen generate-model --provider tripo --mode text --prompt "a red chair"
unity-mcp asset-gen status --job-id abc123
unity-mcp asset-gen import-model-file --source-path "C:/exports/house.fbx" --output-folder Assets/Models
unity-mcp blender status # Is Blender reachable?
unity-mcp blender import-model --selection-only --target-size 2
```

### Raw Commands

For advanced usage, send raw tool calls:
Expand Down
24 changes: 24 additions & 0 deletions Server/tests/test_manifest_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""manifest.json (the MCP bundle manifest) must list every registered tool, and only those.

The six asset_gen tools shipped without entries because nothing compared the two lists.
"""
import json
from pathlib import Path

import services.tools as tools_package
from services.registry import get_registered_tools
from utils.module_discovery import discover_modules


MANIFEST = Path(__file__).resolve().parents[2] / "manifest.json"


def test_manifest_lists_exactly_the_registered_tools():
# Import every tool module so its @mcp_for_unity_tool decorator runs (see test_tool_annotations).
list(discover_modules(Path(tools_package.__file__).parent, tools_package.__package__))
registered = {tool["name"] for tool in get_registered_tools()}
listed = [tool["name"] for tool in json.loads(MANIFEST.read_text(encoding="utf-8"))["tools"]]

assert len(listed) == len(set(listed)), f"manifest.json lists a tool twice: {sorted(listed)}"
assert sorted(registered - set(listed)) == [], "registered tools that manifest.json does not list"
assert sorted(set(listed) - registered) == [], "manifest.json lists tools that are not registered"
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Collections;
using System.Text.RegularExpressions;
using System.Threading;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using MCPForUnity.Editor.Services.Transport;

namespace MCPForUnityTests.Editor.Services
{
[TestFixture]
public class TransportCommandDispatcherTests
{
[UnityTest]
public IEnumerator UnknownCommand_IsAnsweredWithoutAnErrorInTheConsole()
{
// A server or CLI newer than the package sends commands this package does not have.
// That reached the console as a red error with a stack trace, as if the Editor broke.
LogAssert.Expect(LogType.Warning, new Regex("no_such_command.*update the package"));

var reply = TransportCommandDispatcher.ExecuteCommandJsonAsync(
"{\"type\":\"no_such_command\",\"params\":{}}", CancellationToken.None);
for (int frame = 0; frame < 600 && !reply.IsCompleted; frame++)
{
yield return null;
}

Assert.IsTrue(reply.IsCompleted, "the dispatcher never answered");
var response = JObject.Parse(reply.Result);
Assert.AreEqual("error", (string)response["status"]);
StringAssert.Contains("'no_such_command'", (string)response["error"]);
Assert.AreEqual("no_such_command", (string)response["command"]);
Assert.IsNull((string)response["stackTrace"]);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
"name": "batch_execute",
"description": "Execute multiple Unity operations in a single batch"
},
{
"name": "blender_bridge",
"description": "Drive a running Blender through the BlenderMCP addon and bring its results into Unity"
},
{
"name": "create_script",
"description": "Create new C# scripts"
Expand Down Expand Up @@ -69,6 +73,18 @@
"name": "find_in_file",
"description": "Search for content within Unity project files"
},
{
"name": "generate_audio",
"description": "Generate sound effects and music with AI models and import them as AudioClips"
},
{
"name": "generate_image",
"description": "Generate 2D images with AI providers and import them as textures or sprites"
},
{
"name": "generate_model",
"description": "Generate 3D models with AI providers and import them into the project"
},
{
"name": "get_sha",
"description": "Get SHA hash of script content"
Expand All @@ -77,6 +93,14 @@
"name": "get_test_job",
"description": "Get status of async test job"
},
{
"name": "import_model",
"description": "Import 3D models from the Sketchfab marketplace into the project"
},
{
"name": "import_model_file",
"description": "Import a 3D model file that is already on disk (FBX, OBJ, glTF) into the project"
},
{
"name": "manage_animation",
"description": "Manage Unity animation: Animator control, AnimatorController CRUD, and AnimationClip operations"
Expand Down
20 changes: 20 additions & 0 deletions tools/tests/test_python_tests_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""A Codecov outage must not fail the Python test job, which gates both release pipelines."""
from pathlib import Path
import re


WORKFLOW = Path(__file__).resolve().parents[2] / ".github" / "workflows" / "python-tests.yml"


def coverage_upload_steps():
text = WORKFLOW.read_text(encoding="utf-8")
starts = [match.start() for match in re.finditer(r"^ - ", text, re.M)] + [len(text)]
steps = [text[begin:end] for begin, end in zip(starts, starts[1:])]
return [step for step in steps if "codecov/codecov-action@" in step]


def test_coverage_upload_cannot_fail_the_job():
steps = coverage_upload_steps()
assert len(steps) == 1
# fail_ci_if_error: false does not stop codecov-action v4 crashing on a download error.
assert re.search(r"^ continue-on-error: true$", steps[0], re.M), steps[0]
42 changes: 42 additions & 0 deletions website/docs/guides/cli-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,48 @@ unity-mcp batch inline '[{"tool": "manage_scene", "params": {...}}]'
unity-mcp batch template > commands.json
```

**Animation**
```bash
unity-mcp animation animator play "Player" "Run"
unity-mcp animation animator set-parameter "Player" "Speed" 1.5
unity-mcp animation clip create "Assets/Animations/Bounce.anim" --length 2.0 --loop
unity-mcp animation controller info "Assets/Animations/Player.controller"
```

**Build**
```bash
unity-mcp build platform [android] # Read or switch platform
unity-mcp build run --target windows64 --development
unity-mcp build status
```

**Physics**
```bash
unity-mcp physics raycast --origin "0,5,0" --direction "0,-1,0"
unity-mcp physics simulate --steps 10
unity-mcp physics validate
```

**Profiler**
```bash
unity-mcp profiler start
unity-mcp profiler get-counters --category Render
unity-mcp profiler stop
```

**Reflection & Docs**
```bash
unity-mcp reflect type NavMeshAgent
unity-mcp docs get Physics Raycast
```

**Asset Generation & Blender**
```bash
unity-mcp asset-gen generate-image --provider fal --prompt "a stone texture"
unity-mcp asset-gen status --job-id abc123
unity-mcp blender import-model --selection-only --target-size 2
```

**Raw Access (Any Tool)**
```bash
unity-mcp raw tool_name 'JSON_params'
Expand Down
Loading
Loading