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
3 changes: 3 additions & 0 deletions MCPForUnity/Editor/Dependencies/DependencyManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ public static DependencyCheckResult CheckAllDependencies()
var uvStatus = detector.DetectUv();
result.Dependencies.Add(uvStatus);

// Check git (optional: Package Manager Git-URL installs only)
result.Dependencies.Add(detector.DetectGit());

// Generate summary and recommendations
result.GenerateSummary();
GenerateRecommendations(result, detector);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public interface IPlatformDetector
/// </summary>
DependencyStatus DetectUv();

/// <summary>
/// Detect git on this platform. Optional: only the Package Manager's Git-URL install path needs it.
/// </summary>
DependencyStatus DetectGit();

/// <summary>
/// Get platform-specific installation recommendations
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,67 @@ public virtual DependencyStatus DetectUv()
}


// Git is not needed to run the bridge, only to add or update the package from a Git URL
// in the Package Manager, which is the install path most users take (issue #1216). It is
// reported as optional so a missing git never blocks setup, but the row tells the user why
// "Error when executing git command" appeared and how to clear it.
public const string GitInstallUrl = "https://git-scm.com/downloads";

public virtual DependencyStatus DetectGit()
{
var status = new DependencyStatus("Git", isRequired: false)
{
InstallationHint = GitInstallUrl
};

try
{
if (!TryFindInPath("git", out string gitPath))
{
status.ErrorMessage = "git not found";
status.Details = "Only needed to add or update MCP for Unity from a Git URL in the Package Manager.";
return status;
}

if (ExecPath.TryRun(gitPath, "--version", null, out string stdout, out string stderr, 5000)
&& TryParseGitVersion(string.IsNullOrWhiteSpace(stdout) ? stderr : stdout, out string version))
{
status.IsAvailable = true;
status.Version = version;
status.Path = gitPath;
status.Details = "If the Package Manager still reports 'not in a git directory', git is refusing a folder "
+ "owned by another user: run git config --global --add safe.directory \"<your Unity project folder>\"";
return status;
}

status.ErrorMessage = "git found but did not report a version";
status.Path = gitPath;
}
catch (Exception ex)
{
status.ErrorMessage = $"Error detecting git: {ex.Message}";
}

return status;
}

/// <summary>Parses "git version 2.45.1.windows.1" or "git version 2.39.5 (Apple Git-154)" into "2.45.1.windows.1" / "2.39.5".</summary>
internal static bool TryParseGitVersion(string output, out string version)
{
version = null;
string line = (output ?? string.Empty).Trim();
const string prefix = "git version ";
if (!line.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
return false;
}

string rest = line.Substring(prefix.Length).Trim();
int end = rest.IndexOfAny(new[] { ' ', '\r', '\n' });
version = end >= 0 ? rest.Substring(0, end) : rest;
return version.Length > 0 && char.IsDigit(version[0]);
}

protected bool TryParseVersion(string version, out int major, out int minor)
{
major = 0;
Expand Down
28 changes: 26 additions & 2 deletions MCPForUnity/Editor/Windows/MCPSetupWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public class MCPSetupWindow : EditorWindow
private VisualElement uvIndicator;
private Label uvVersion;
private Label uvDetails;
private VisualElement gitIndicator;
private Label gitVersion;
private Label gitDetails;
private Label statusMessage;
private VisualElement installationSection;
private Label installationInstructions;
Expand Down Expand Up @@ -88,6 +91,9 @@ public void CreateGUI()
uvIndicator = rootVisualElement.Q<VisualElement>("uv-indicator");
uvVersion = rootVisualElement.Q<Label>("uv-version");
uvDetails = rootVisualElement.Q<Label>("uv-details");
gitIndicator = rootVisualElement.Q<VisualElement>("git-indicator");
gitVersion = rootVisualElement.Q<Label>("git-version");
gitDetails = rootVisualElement.Q<Label>("git-details");
statusMessage = rootVisualElement.Q<Label>("status-message");
installationSection = rootVisualElement.Q<VisualElement>("installation-section");
installationInstructions = rootVisualElement.Q<Label>("installation-instructions");
Expand Down Expand Up @@ -328,6 +334,13 @@ private void UpdateUI()
UpdateDependencyStatus(uvIndicator, uvVersion, uvDetails, uvDep);
}

// Update git status (optional dependency: never blocks readiness)
var gitDep = _dependencyResult.Dependencies.Find(d => d.Name == "Git");
if (gitDep != null)
{
UpdateDependencyStatus(gitIndicator, gitVersion, gitDetails, gitDep);
}

// Offer the one-click uv installer only when uv is actually missing
bool uvMissing = uvDep != null && !uvDep.IsAvailable;
if (installUvButton != null)
Expand All @@ -352,7 +365,7 @@ private void UpdateUI()
}
}

private void UpdateDependencyStatus(VisualElement indicator, Label versionLabel, Label detailsLabel, DependencyStatus dep)
internal static void UpdateDependencyStatus(VisualElement indicator, Label versionLabel, Label detailsLabel, DependencyStatus dep)
{
if (dep.IsAvailable)
{
Expand All @@ -362,14 +375,25 @@ private void UpdateDependencyStatus(VisualElement indicator, Label versionLabel,
detailsLabel.text = dep.Details ?? "Available";
detailsLabel.style.color = new StyleColor(Color.gray);
}
else
else if (dep.IsRequired)
{
indicator.RemoveFromClassList("valid");
indicator.AddToClassList("invalid");
versionLabel.text = "Not Found";
detailsLabel.text = dep.ErrorMessage ?? "Not available";
detailsLabel.style.color = new StyleColor(Color.red);
}
else
{
// A missing optional dependency is information, not a blocker. Drop both state
// classes so the dot keeps the neutral grey of .status-indicator-small instead of
// the red .invalid reserved for required ones, and say what it is for.
indicator.RemoveFromClassList("valid");
indicator.RemoveFromClassList("invalid");
versionLabel.text = "Not Found";
detailsLabel.text = dep.Details ?? dep.ErrorMessage ?? "Not available";
detailsLabel.style.color = new StyleColor(Color.gray);
}
}
}
}
12 changes: 11 additions & 1 deletion MCPForUnity/Editor/Windows/MCPSetupWindow.uxml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<ui:VisualElement class="section">
<ui:Label text="System Requirements" class="section-title" />
<ui:VisualElement class="section-content">
<ui:Label text="MCP for Unity requires Python 3.10+ and UV package manager to function." class="help-text description-text" />
<ui:Label text="MCP for Unity requires Python 3.10+ and UV package manager to function. Git is only needed to install or update the package from a Git URL." class="help-text description-text" />

<!-- Dependency Status -->
<ui:VisualElement name="dependency-list">
Expand All @@ -34,6 +34,16 @@
</ui:VisualElement>
<ui:Label name="uv-details" class="help-text dependency-details" />
</ui:VisualElement>

<!-- Git Status (optional) -->
<ui:VisualElement class="dependency-item">
<ui:VisualElement class="dependency-row">
<ui:Label text="Git (optional)" class="dependency-name" />
<ui:Label name="git-version" text="..." class="setting-value" />
<ui:VisualElement name="git-indicator" class="status-indicator-small" />
</ui:VisualElement>
<ui:Label name="git-details" class="help-text dependency-details" />
</ui:VisualElement>
</ui:VisualElement>

<!-- Overall Status Message -->
Expand Down
133 changes: 133 additions & 0 deletions TestProjects/UnityMCPTests/Assets/Tests/EditMode/GitDetectionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
using System.Linq;
using MCPForUnity.Editor.Dependencies;
using MCPForUnity.Editor.Dependencies.Models;
using MCPForUnity.Editor.Dependencies.PlatformDetectors;
using MCPForUnity.Editor.Windows;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.UIElements;

namespace MCPForUnityTests.Editor
{
public class GitDetectionTests
{
[TestCase("git version 2.45.1.windows.1", "2.45.1.windows.1")]
[TestCase("git version 2.39.5 (Apple Git-154)", "2.39.5")]
[TestCase(" git version 2.43.0\n", "2.43.0")]
public void TryParseGitVersion_ExtractsTheVersionToken(string output, string expected)
{
Assert.IsTrue(PlatformDetectorBase.TryParseGitVersion(output, out string version));
Assert.AreEqual(expected, version);
}

[TestCase("")]
[TestCase(null)]
[TestCase("'git' is not recognized as an internal or external command")]
[TestCase("git version ")]
[TestCase("git version beta")]
public void TryParseGitVersion_RejectsAnythingThatIsNotAGitVersionLine(string output)
{
Assert.IsFalse(PlatformDetectorBase.TryParseGitVersion(output, out _));
}

[Test]
public void CheckAllDependencies_ReportsGitAsOptional()
{
var result = DependencyManager.CheckAllDependencies();
var git = result.Dependencies.FirstOrDefault(d => d.Name == "Git");

Assert.IsNotNull(git, "The dependency check should always include a Git row");
Assert.IsFalse(git.IsRequired, "Git must never block setup; only Git-URL installs need it");
Assert.AreEqual(PlatformDetectorBase.GitInstallUrl, git.InstallationHint);
if (git.IsAvailable)
{
Assert.IsFalse(string.IsNullOrEmpty(git.Version));
Assert.IsFalse(string.IsNullOrEmpty(git.Path));
}
else
{
Assert.IsFalse(string.IsNullOrEmpty(git.ErrorMessage));
}
}

[Test]
public void MissingGit_DoesNotMakeTheSystemNotReady()
{
// Mirrors what the setup window computes: Python and uv present, git absent.
var result = new DependencyCheckResult();
result.Dependencies.Add(new DependencyStatus("Python") { IsAvailable = true, Version = "3.12.0" });
result.Dependencies.Add(new DependencyStatus("uv Package Manager") { IsAvailable = true, Version = "0.8.0" });
result.Dependencies.Add(new DependencyStatus("Git", isRequired: false) { IsAvailable = false, ErrorMessage = "git not found" });

result.GenerateSummary();

Assert.IsTrue(result.IsSystemReady);
Assert.IsTrue(result.HasMissingOptional);
Assert.IsEmpty(result.GetMissingRequired());
}

[Test]
public void MissingOptionalRow_KeepsTheNeutralIndicatorAndExplainsWhatItIsFor()
{
var indicator = new VisualElement();
indicator.AddToClassList("status-indicator-small");
indicator.AddToClassList("valid"); // stale state from an earlier refresh
var version = new Label();
var details = new Label();
var dep = new DependencyStatus("Git", isRequired: false)
{
IsAvailable = false,
ErrorMessage = "git not found",
Details = "Only needed to add or update MCP for Unity from a Git URL in the Package Manager."
};

MCPSetupWindow.UpdateDependencyStatus(indicator, version, details, dep);

// .status-indicator-small.invalid is red in Common.uss; an optional row must keep the
// plain grey of the base class so it does not read as a blocker.
Assert.IsFalse(indicator.ClassListContains("invalid"));
Assert.IsFalse(indicator.ClassListContains("valid"));
Assert.AreEqual(dep.Details, details.text);
Assert.AreEqual(Color.gray, details.style.color.value);
}

[Test]
public void MissingRequiredRow_StaysRed()
{
var indicator = new VisualElement();
indicator.AddToClassList("status-indicator-small");
var version = new Label();
var details = new Label();
var dep = new DependencyStatus("Python") { IsAvailable = false, ErrorMessage = "Python not found" };

MCPSetupWindow.UpdateDependencyStatus(indicator, version, details, dep);

Assert.IsTrue(indicator.ClassListContains("invalid"));
Assert.AreEqual("Python not found", details.text);
Assert.AreEqual(Color.red, details.style.color.value);
}

[Test]
public void AvailableGitRow_ShowsTheVersionAndTheSafeDirectoryRemedy()
{
var indicator = new VisualElement();
indicator.AddToClassList("status-indicator-small");
indicator.AddToClassList("invalid"); // stale state from an earlier refresh
var version = new Label();
var details = new Label();
var dep = new DependencyStatus("Git", isRequired: false)
{
IsAvailable = true,
Version = "2.45.1",
Details = "run git config --global --add safe.directory \"<your Unity project folder>\""
};

MCPSetupWindow.UpdateDependencyStatus(indicator, version, details, dep);

Assert.IsTrue(indicator.ClassListContains("valid"));
Assert.IsFalse(indicator.ClassListContains("invalid"));
Assert.AreEqual("v2.45.1", version.text);
Assert.AreEqual(dep.Details, details.text);
}
}
}

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

2 changes: 2 additions & 0 deletions website/docs/getting-started/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Three install paths are supported. Pick one. **Git URL** is the fastest if you j

## Option 1 — Git URL (fastest)

This path needs `git` on your PATH (the Package Manager runs it). If it reports `Error when executing git command`, see [troubleshooting](../guides/troubleshooting.md#package-manager-error-when-executing-git-command--not-in-a-git-directory).

In Unity, open **Window → Package Manager**, click the **`+`** button, choose **Add package from git URL...**, and paste:

```text
Expand Down
21 changes: 21 additions & 0 deletions website/docs/guides/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,27 @@ This is a Unity bug (UUM-132096), not an MCP for Unity one.

---

## Package Manager: "Error when executing git command" / "not in a git directory"

Adding the package from a Git URL makes the Package Manager shell out to `git`. Two things make that fail:

1. **git is not installed or not on PATH.** Install it from [git-scm.com](https://git-scm.com/downloads) and restart Unity so the Editor picks up the new PATH. The setup window (**Window → MCP for Unity → Local Setup Window**) shows a **Git (optional)** row so you can confirm the Editor sees it.
2. **git refuses the folder.** Newer git versions decline to run inside a directory owned by a different user account (external drives, shared folders, projects created by another account). The Package Manager surfaces this as `fatal: not in a git directory`. Tell git the folder is yours:

```bash
# trust this one project
git config --global --add safe.directory "/path/to/the/unity/project"

# or trust every repository under a folder — the trailing /* is required
git config --global --add safe.directory "/path/to/the/parent/folder/*"
```

A plain directory path only trusts that exact repository; the `/*` suffix is what extends it to the repositories underneath. Restart Unity and add the package again.

Git is only needed for this install path; the bridge itself does not use it, so a missing git never blocks setup.

*Reported by [@Cherrymocha](https://github.com/CoplayDev/unity-mcp/issues/1216).*

## Codex: `resources/read failed: unknown MCP server`

The `mcpforunity://` URI names the *resource*, not the server. Some clients take a separate server key on a resource read.
Expand Down
Loading