using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
using Debug = UnityEngine.Debug;
namespace SynapticPro
{
///
/// One-touch MCP server setup manager
/// Handles automatic installation of Git, Node.js, npm and various AI configurations
///
public class NexusMCPSetupManager
{
private static NexusMCPSetupManager instance;
private static int mcpServerProcessId = -1;
public static NexusMCPSetupManager Instance
{
get
{
if (instance == null)
instance = new NexusMCPSetupManager();
return instance;
}
}
private string projectPath;
private string mcpServerPath;
private string toolsPath;
public class SetupStatus
{
public bool isGitInstalled;
public bool isNodeInstalled;
public bool isNpmInstalled;
public bool isMCPInstalled;
public bool isConfigured;
public string gitVersion;
public string nodeVersion;
public string npmVersion;
public List installedTools = new List();
public Dictionary aiConfigurations = new Dictionary();
}
private SetupStatus currentStatus = new SetupStatus();
private NexusMCPSetupManager()
{
projectPath = Application.dataPath.Replace("/Assets", "");
mcpServerPath = Path.Combine(projectPath, "MCPServer");
toolsPath = Path.Combine(projectPath, "Tools");
}
///
/// Check setup status
///
public async Task CheckSetupStatus()
{
currentStatus = new SetupStatus();
// Check Git
currentStatus.gitVersion = await CheckCommand("git", "--version");
currentStatus.isGitInstalled = !string.IsNullOrEmpty(currentStatus.gitVersion);
// Check Node.js
currentStatus.nodeVersion = await CheckCommand("node", "--version");
currentStatus.isNodeInstalled = !string.IsNullOrEmpty(currentStatus.nodeVersion);
// Check npm
currentStatus.npmVersion = await CheckCommand("npm", "--version");
currentStatus.isNpmInstalled = !string.IsNullOrEmpty(currentStatus.npmVersion);
// Check MCP server directory and version
if (Directory.Exists(mcpServerPath) && File.Exists(Path.Combine(mcpServerPath, "package.json")))
{
try
{
// Check package.json version
var packageJsonPath = Path.Combine(mcpServerPath, "package.json");
var packageJsonContent = File.ReadAllText(packageJsonPath);
// Simple version check (avoid JSON parsing dependency)
if (packageJsonContent.Contains("\"version\": \"1.2.0\"") ||
packageJsonContent.Contains("\"version\":\"1.2.0\""))
{
currentStatus.isMCPInstalled = true;
Debug.Log("[Synaptic] MCPServer v1.2.0 detected - up to date");
}
else
{
// Old version detected - force regeneration
currentStatus.isMCPInstalled = false;
Debug.LogWarning("[Synaptic] MCPServer outdated version detected - will regenerate");
}
}
catch (Exception e)
{
Debug.LogWarning($"[Synaptic] Failed to check MCPServer version: {e.Message}");
currentStatus.isMCPInstalled = false;
}
}
else
{
currentStatus.isMCPInstalled = false;
}
// Check AI configurations
CheckAIConfigurations();
return currentStatus;
}
///
/// Execute one-touch setup
///
public async Task RunCompleteSetup(Action progressCallback = null)
{
try
{
progressCallback?.Invoke("Starting setup...");
// 1. Install necessary tools
if (!currentStatus.isGitInstalled)
{
progressCallback?.Invoke("Installing Git...");
await InstallGit();
}
if (!currentStatus.isNodeInstalled || !currentStatus.isNpmInstalled)
{
progressCallback?.Invoke("Installing Node.js and npm...");
await InstallNodeJS();
}
// 2. Setup MCP server
progressCallback?.Invoke("Building MCP Server...");
await SetupMCPServer();
// 3. Install dependencies
progressCallback?.Invoke("Installing dependencies...");
await InstallDependencies();
// 4. Install Unity integration tools
progressCallback?.Invoke("Setting up Unity integration tools...");
await SetupUnityTools();
// 5. AI configuration
progressCallback?.Invoke("Configuring AI settings...");
await ConfigureAIServices();
// 6. Generate configuration files
progressCallback?.Invoke("Generating configuration files...");
await GenerateConfigFiles();
progressCallback?.Invoke("Setup complete!");
// Check final state
await CheckSetupStatus();
return true;
}
catch (Exception e)
{
Debug.LogError($"[MCP Setup] Setup error: {e.Message}");
progressCallback?.Invoke($"Error: {e.Message}");
return false;
}
}
///
/// Auto-install Git
///
private Task InstallGit()
{
return Task.Run(async () =>
{
var platform = Application.platform;
if (platform == RuntimePlatform.OSXEditor)
{
// macOS - Install via Homebrew
var hasHomebrew = await CheckCommand("brew", "--version");
if (string.IsNullOrEmpty(hasHomebrew))
{
// Let user install Homebrew
#if UNITY_EDITOR
EditorUtility.DisplayDialog(
"Homebrew Required",
"Homebrew is not installed.\nPlease install from https://brew.sh",
"OK"
);
#endif
Application.OpenURL("https://brew.sh");
throw new Exception("Homebrew is not installed");
}
await RunCommand("brew", "install git");
}
else if (platform == RuntimePlatform.WindowsEditor)
{
// Windows - Download Git for Windows
var gitInstallerPath = Path.Combine(toolsPath, "GitInstaller.exe");
if (!Directory.Exists(toolsPath))
Directory.CreateDirectory(toolsPath);
// Download Git for Windows
// Note: Git download page is HTML, so use direct download URL
using (var client = new System.Net.WebClient())
{
// Use latest 2.51.0 version
var gitDownloadUrl = "https://github.com/git-for-windows/git/releases/download/v2.51.0.windows.1/Git-2.51.0-64-bit.exe";
await client.DownloadFileTaskAsync(gitDownloadUrl, gitInstallerPath);
}
// Silent install
await RunCommand(gitInstallerPath, "/VERYSILENT /NORESTART");
}
// Update path
RefreshEnvironmentPath();
});
}
///
/// Auto-install Node.js
///
private Task InstallNodeJS()
{
return Task.Run(async () =>
{
var platform = Application.platform;
if (platform == RuntimePlatform.OSXEditor)
{
// macOS - via Homebrew
await RunCommand("brew", "install node");
}
else if (platform == RuntimePlatform.WindowsEditor)
{
// Windows - Download Node.js installer
var nodeInstallerPath = Path.Combine(toolsPath, "NodeInstaller.msi");
if (!Directory.Exists(toolsPath))
Directory.CreateDirectory(toolsPath);
// Download Node.js LTS v22.11.0
using (var client = new System.Net.WebClient())
{
await client.DownloadFileTaskAsync(
"https://nodejs.org/dist/v22.11.0/node-v22.11.0-x64.msi",
nodeInstallerPath
);
}
// Silent install
await RunCommand("msiexec", $"/i \"{nodeInstallerPath}\" /qn");
}
RefreshEnvironmentPath();
});
}
///
/// Setup MCP server
///
private Task SetupMCPServer()
{
return Task.Run(() =>
{
// Create MCP server directory
if (!Directory.Exists(mcpServerPath))
{
Directory.CreateDirectory(mcpServerPath);
}
// Generate package.json (ES module support)
var packageJson = @"{
""name"": ""unity-mcp-server"",
""version"": ""1.2.0"",
""description"": ""MCP Server for Unity Integration"",
""main"": ""index.js"",
""type"": ""module"",
""scripts"": {
""start"": ""node index.js"",
""dev"": ""nodemon index.js""
},
""dependencies"": {
""@modelcontextprotocol/sdk"": ""^1.18.1"",
""express"": ""^4.18.2"",
""ws"": ""^8.13.0"",
""cors"": ""^2.8.5"",
""dotenv"": ""^16.0.3"",
""zod"": ""^3.23.8""
},
""devDependencies"": {
""nodemon"": ""^3.0.1""
}
}";
File.WriteAllText(Path.Combine(mcpServerPath, "package.json"), packageJson);
// Generate MCP server main file (ES module support)
var serverCode = @"import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import express from 'express';
import cors from 'cors';
import { WebSocketServer } from 'ws';
import dotenv from 'dotenv';
dotenv.config();
const app = express();
app.use(cors());
app.use(express.json());
// Initialize MCP server
const server = new Server(
{
name: 'unity-mcp-server',
version: '1.2.0',
},
{
capabilities: {
tools: {},
},
}
);
// Unity operation tools
server.setRequestHandler('tools/list', async () => {
return {
tools: [
{
name: 'unity_create',
description: 'Create Unity GameObjects and components',
inputSchema: {
type: 'object',
properties: {
objectType: { type: 'string' },
name: { type: 'string' },
position: {
type: 'object',
properties: {
x: { type: 'number' },
y: { type: 'number' },
z: { type: 'number' }
}
}
},
},
},
],
};
});
server.setRequestHandler('tools/call', async (request) => {
if (request.params.name === 'unity_create') {
return await sendUnityCommand('create', request.params.arguments);
}
throw new Error(`Unknown tool: ${request.params.name}`);
});
// WebSocket server
const wss = new WebSocketServer({ port: 8090 });
wss.on('connection', (ws) => {
console.log('Unity client connected');
ws.on('message', (message) => {
console.log('Received from Unity:', message.toString());
});
});
async function sendUnityCommand(command, params) {
// Send to Unity client
const message = JSON.stringify({ command, params });
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
return { success: true };
}
// HTTP endpoints
app.post('/api/chat', async (req, res) => {
try {
const { message } = req.body;
// Simple echo response
res.json({ response: `Unity MCP Server received: ${message}` });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Unity MCP Server running on port ${PORT}`);
console.log(`WebSocket server running on port 8090`);
});
// Start MCP server via Stdio
async function runMCPServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.log('MCP Server connected via stdio');
}
// Start both servers
runMCPServer().catch(console.error);
";
File.WriteAllText(Path.Combine(mcpServerPath, "index.js"), serverCode);
// Generate .env file
var envContent = @"PORT=3000
CLAUDE_API_KEY=
GEMINI_API_KEY=
OPENAI_API_KEY=
";
File.WriteAllText(Path.Combine(mcpServerPath, ".env"), envContent);
});
}
///
/// Install dependencies
///
private async Task InstallDependencies()
{
// Run npm install in MCP server directory
await RunCommand("npm", "install", mcpServerPath);
// Install MCP-related tools
// Claude CLI doesn't officially exist as npm package, so commented out
// await RunCommand("npm", "install -g claude-cli");
}
///
/// Setup Unity integration tools
///
private async Task SetupUnityTools()
{
// Create Unity WebSocket client
var clientCode = @"using System;
using System.Collections.Generic;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using Newtonsoft.Json;
namespace SynapticPro.MCP
{
public class MCPUnityClient : MonoBehaviour
{
private ClientWebSocket ws;
private CancellationTokenSource cancellationToken;
private string serverUrl = ""ws://localhost:8090"";
private Queue commandQueue = new Queue();
private bool isConnected = false;
[Serializable]
public class MCPCommand
{
public string command;
public Dictionary parameters;
}
void Start()
{
ConnectToMCPServer();
}
async void ConnectToMCPServer()
{
try
{
ws = new ClientWebSocket();
cancellationToken = new CancellationTokenSource();
await ws.ConnectAsync(new Uri(serverUrl), cancellationToken.Token);
isConnected = true;
Debug.Log(""[MCP Client] Connected to MCP Server"");
// Start message receiving loop
_ = ReceiveLoop();
}
catch (Exception e)
{
Debug.LogError($""[MCP Client] Connection error: {e.Message}"");
}
}
async Task ReceiveLoop()
{
var buffer = new ArraySegment(new byte[4096]);
while (isConnected && ws.State == WebSocketState.Open)
{
try
{
var result = await ws.ReceiveAsync(buffer, cancellationToken.Token);
if (result.MessageType == WebSocketMessageType.Text)
{
var message = Encoding.UTF8.GetString(buffer.Array, 0, result.Count);
var command = JsonConvert.DeserializeObject(message);
commandQueue.Enqueue(command);
}
}
catch (Exception e)
{
Debug.LogError($""[MCP Client] Receive error: {e.Message}"");
isConnected = false;
}
}
}
void Update()
{
while (commandQueue.Count > 0)
{
var command = commandQueue.Dequeue();
ExecuteCommand(command);
}
}
void ExecuteCommand(MCPCommand command)
{
switch (command.command)
{
case ""create"":
CreateGameObject(command.parameters);
break;
// Add other commands
}
}
void CreateGameObject(Dictionary parameters)
{
var name = parameters.ContainsKey(""name"") ? parameters[""name""].ToString() : ""GameObject"";
var go = new GameObject(name);
if (parameters.ContainsKey(""position""))
{
// Set position
}
Debug.Log($""[MCP Client] Created GameObject: {name}"");
}
async void OnDestroy()
{
if (ws != null && ws.State == WebSocketState.Open)
{
cancellationToken.Cancel();
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, ""Client closing"", CancellationToken.None);
ws.Dispose();
}
}
}
}";
var clientPath = Path.Combine(Application.dataPath, "Synaptic", "Scripts", "MCP", "MCPUnityClient.cs");
var mcpDir = Path.GetDirectoryName(clientPath);
if (!Directory.Exists(mcpDir))
{
Directory.CreateDirectory(mcpDir);
}
File.WriteAllText(clientPath, clientCode);
// Install required packages
await InstallUnityPackage("com.unity.nuget.newtonsoft-json", "3.2.1");
}
///
/// Auto-configure AI services
///
private async Task ConfigureAIServices()
{
var configPath = Path.Combine(mcpServerPath, "ai-config");
if (!Directory.Exists(configPath))
{
Directory.CreateDirectory(configPath);
}
// Claude configuration
var claudeConfig = @"{
""provider"": ""anthropic"",
""model"": ""claude-3-opus-20240229"",
""temperature"": 0.7,
""max_tokens"": 4096,
""tools"": [""unity_create"", ""unity_modify"", ""unity_delete""]
}";
File.WriteAllText(Path.Combine(configPath, "claude.json"), claudeConfig);
// Gemini configuration
var geminiConfig = @"{
""provider"": ""google"",
""model"": ""gemini-pro"",
""temperature"": 0.8,
""tools"": [""unity_create"", ""unity_modify""]
}";
File.WriteAllText(Path.Combine(configPath, "gemini.json"), geminiConfig);
// Copilot configuration
var copilotConfig = @"{
""provider"": ""github"",
""model"": ""gpt-4"",
""temperature"": 0.5,
""tools"": [""unity_create"", ""unity_modify"", ""code_generation""]
}";
File.WriteAllText(Path.Combine(configPath, "copilot.json"), copilotConfig);
currentStatus.aiConfigurations["Claude"] = true;
currentStatus.aiConfigurations["Gemini"] = true;
currentStatus.aiConfigurations["Copilot"] = true;
await Task.CompletedTask;
}
///
/// Generate configuration files
///
private async Task GenerateConfigFiles()
{
// MCP configuration file
var mcpConfig = @"{
""servers"": {
""unity"": {
""command"": ""node"",
""args"": [""index.js""],
""cwd"": """ + mcpServerPath.Replace("\\", "/") + @""",
""env"": {
""NODE_ENV"": ""production""
}
}
},
""tools"": {
""unity_create"": {
""description"": ""Create Unity GameObjects""
},
""unity_modify"": {
""description"": ""Modify Unity GameObjects""
},
""unity_delete"": {
""description"": ""Delete Unity GameObjects""
}
}
}";
// Save MCP configuration file in project
var mcpConfigPath = Path.Combine(mcpServerPath, "mcp-config.json");
File.WriteAllText(mcpConfigPath, mcpConfig);
// Generate README with documentation links for reference
var readmeContent = @"# Unity MCP Server
## Documentation
- MCP Documentation: https://modelcontextprotocol.io/docs
- MCP Specification: https://modelcontextprotocol.io/specification/2025-06-18
- MCP SDK: https://www.npmjs.com/package/@modelcontextprotocol/sdk
## API Keys
- Claude API: https://console.anthropic.com/
- Gemini API: https://aistudio.google.com/app/apikey
- OpenAI API: https://platform.openai.com/api-keys
## Setup
1. Run `npm install` to install dependencies
2. Configure API keys in .env file
3. Run `npm start` to start the server
";
File.WriteAllText(Path.Combine(mcpServerPath, "README.md"), readmeContent);
// Startup script
var startScript = @"#!/bin/bash
cd """ + mcpServerPath + @"""
npm start
";
var startScriptPath = Path.Combine(mcpServerPath, "start.sh");
File.WriteAllText(startScriptPath, startScript);
if (Application.platform == RuntimePlatform.OSXEditor || Application.platform == RuntimePlatform.LinuxEditor)
{
await RunCommand("chmod", $"+x \"{startScriptPath}\"");
}
}
///
/// Command execution helper
///
private async Task CheckCommand(string command, string args)
{
try
{
var resolvedCommand = ResolveCommandPath(command);
if (string.IsNullOrEmpty(resolvedCommand)) return ""; // Not found, skip
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = resolvedCommand,
Arguments = args,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
process.Start();
var output = await process.StandardOutput.ReadToEndAsync();
process.WaitForExit(5000); // 5秒タイムアウト
if (!process.HasExited) process.Kill();
return output.Trim();
}
catch
{
return "";
}
}
private string ResolveCommandPath(string command)
{
bool isWindows = Application.platform == RuntimePlatform.WindowsEditor;
string executableName = isWindows ? (command.EndsWith(".exe") ? command : command + ".exe") : command;
// First search with which/where command (most reliable)
try
{
var whichResult = isWindows ? RunWhereCommand(command) : RunWhichCommand(command);
if (!string.IsNullOrEmpty(whichResult) && File.Exists(whichResult))
{
Debug.Log($"[MCP Setup] Detected {command}: {whichResult}");
return whichResult;
}
}
catch { }
// Search common command paths (OS-specific)
var commonPaths = new System.Collections.Generic.List();
if (isWindows)
{
var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
var programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
commonPaths.AddRange(new[]
{
// Node.js標準インストール
Path.Combine(programFiles, "nodejs"),
Path.Combine(programFilesX86, "nodejs"),
Path.Combine(localAppData, "Programs", "nodejs"),
// Git
Path.Combine(programFiles, "Git", "cmd"),
Path.Combine(programFiles, "Git", "bin"),
Path.Combine(localAppData, "Programs", "Git", "cmd"),
// nvm-windows
Path.Combine(appData, "nvm"),
Path.Combine(userProfile, ".nvm"),
// volta
Path.Combine(localAppData, "Volta", "bin"),
Path.Combine(userProfile, ".volta", "bin"),
// fnm
Path.Combine(localAppData, "fnm"),
Path.Combine(userProfile, ".fnm"),
// npm global
Path.Combine(appData, "npm"),
// D:ドライブ(セカンダリドライブにインストールするユーザー対策)
"D:\\nodejs",
"D:\\Program Files\\nodejs",
"D:\\Program Files (x86)\\nodejs",
"D:\\Program Files\\Git\\cmd",
"D:\\nvm",
"D:\\nvm\\nodejs",
// E:ドライブ
"E:\\nodejs",
"E:\\Program Files\\nodejs",
// その他のドライブ(F-I)
"F:\\nodejs", "F:\\Program Files\\nodejs",
"G:\\nodejs", "G:\\Program Files\\nodejs",
"H:\\nodejs", "H:\\Program Files\\nodejs",
"I:\\nodejs", "I:\\Program Files\\nodejs",
});
}
else
{
var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
commonPaths.AddRange(new[]
{
"/opt/homebrew/bin",
"/usr/local/bin",
"/usr/bin",
"/bin",
"/usr/local/Cellar",
Path.Combine(home, ".nvm", "versions", "node"),
Path.Combine(home, ".volta", "bin"),
Path.Combine(home, ".fnm"),
Path.Combine(home, ".npm-global", "bin")
});
}
foreach (var basePath in commonPaths)
{
if (Directory.Exists(basePath))
{
// Direct check (with and without .exe)
var directPath = Path.Combine(basePath, executableName);
if (File.Exists(directPath))
{
Debug.Log($"[MCP Setup] Detected {command}: {directPath}");
return directPath;
}
// Try without .exe on Windows (some tools don't have .exe)
if (isWindows)
{
var noExtPath = Path.Combine(basePath, command);
if (File.Exists(noExtPath))
{
Debug.Log($"[MCP Setup] Detected {command}: {noExtPath}");
return noExtPath;
}
}
// Search subdirectories (for version managers like NVM)
try
{
var subdirs = Directory.GetDirectories(basePath);
foreach (var subdir in subdirs)
{
var binPath = Path.Combine(subdir, "bin", command);
if (File.Exists(binPath))
{
Debug.Log($"[MCP Setup] Detected {command}: {binPath}");
return binPath;
}
}
}
catch { }
}
}
// Search from PATH environment variable
var pathEnv = Environment.GetEnvironmentVariable("PATH");
if (!string.IsNullOrEmpty(pathEnv))
{
var separator = Application.platform == RuntimePlatform.WindowsEditor ? ';' : ':';
foreach (var path in pathEnv.Split(separator))
{
if (!string.IsNullOrEmpty(path))
{
var fullPath = Path.Combine(path.Trim(), executableName);
if (File.Exists(fullPath))
{
Debug.Log($"[MCP Setup] Detected {command} in PATH: {fullPath}");
return fullPath;
}
// Try without .exe
if (isWindows)
{
var noExtPath = Path.Combine(path.Trim(), command);
if (File.Exists(noExtPath))
{
Debug.Log($"[MCP Setup] Detected {command} in PATH: {noExtPath}");
return noExtPath;
}
}
}
}
}
Debug.LogWarning($"[MCP Setup] {command} not found in any known location");
return null; // Not found - return null to prevent hanging Process.Start
}
private string RunWhichCommand(string command)
{
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "/usr/bin/which",
Arguments = command,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
process.Start();
var output = process.StandardOutput.ReadToEnd().Trim();
process.WaitForExit(3000);
if (!process.HasExited) process.Kill();
return process.ExitCode == 0 ? output : "";
}
catch
{
return "";
}
}
private string RunWhereCommand(string command)
{
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "where.exe",
Arguments = command,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
process.Start();
var output = process.StandardOutput.ReadToEnd().Trim();
process.WaitForExit(3000);
if (!process.HasExited) process.Kill();
// whereは複数行返すことがある。最初の行を使う
var firstLine = output.Split('\n')[0].Trim();
return process.ExitCode == 0 && File.Exists(firstLine) ? firstLine : "";
}
catch
{
return "";
}
}
private async Task RunCommand(string command, string args, string workingDir = null)
{
try
{
var resolvedCommand = ResolveCommandPath(command);
Debug.Log($"[MCP Setup] Executing: {resolvedCommand} {args}");
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = resolvedCommand,
Arguments = args,
WorkingDirectory = workingDir ?? projectPath,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
// Set PATH environment variable appropriately
SetupEnvironmentPath(process.StartInfo);
process.Start();
var output = await process.StandardOutput.ReadToEndAsync();
var error = await process.StandardError.ReadToEndAsync();
process.WaitForExit();
if (!string.IsNullOrEmpty(output))
Debug.Log($"[MCP Setup] {output}");
if (!string.IsNullOrEmpty(error))
Debug.LogWarning($"[MCP Setup] {error}");
return process.ExitCode == 0;
}
catch (Exception e)
{
Debug.LogError($"[MCP Setup] Command error: {e.Message}");
return false;
}
}
private void RefreshEnvironmentPath()
{
// Reload environment variables
var path = Environment.GetEnvironmentVariable("PATH");
Environment.SetEnvironmentVariable("PATH", path, EnvironmentVariableTarget.Process);
}
///
/// Start MCP server in background
///
private async Task StartMCPServerBackground(string npmPath)
{
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = npmPath,
Arguments = "start",
WorkingDirectory = mcpServerPath,
UseShellExecute = false,
RedirectStandardOutput = false,
RedirectStandardError = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
}
};
// Set PATH environment variable appropriately
SetupEnvironmentPath(process.StartInfo);
Debug.Log($"[MCP Setup] Starting MCP server in background: {npmPath} start");
process.Start();
// Wait a bit for process to start
await Task.Delay(1000);
mcpServerProcessId = process.Id;
Debug.Log($"[MCP Setup] MCP server process ID: {process.Id}");
}
catch (Exception e)
{
Debug.LogError($"[MCP Setup] Background startup error: {e.Message}");
// Fallback: start directly with node
try
{
var nodePath = ResolveCommandPath("node");
var nodeProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = nodePath,
Arguments = "index.js",
WorkingDirectory = mcpServerPath,
UseShellExecute = false,
RedirectStandardOutput = false,
RedirectStandardError = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
}
};
SetupEnvironmentPath(nodeProcess.StartInfo);
Debug.Log($"[MCP Setup] Fallback: {nodePath} index.js");
nodeProcess.Start();
await Task.Delay(1000);
}
catch (Exception nodeEx)
{
Debug.LogError($"[MCP Setup] Direct Node.js startup also failed: {nodeEx.Message}");
}
}
}
private void CheckAIConfigurations()
{
// Check existence of AI configuration files
var configPath = Path.Combine(mcpServerPath, "ai-config");
if (Directory.Exists(configPath))
{
currentStatus.aiConfigurations["Claude"] = File.Exists(Path.Combine(configPath, "claude.json"));
currentStatus.aiConfigurations["Gemini"] = File.Exists(Path.Combine(configPath, "gemini.json"));
currentStatus.aiConfigurations["Copilot"] = File.Exists(Path.Combine(configPath, "copilot.json"));
}
}
private async Task InstallUnityPackage(string packageId, string version)
{
var manifestPath = Path.Combine(projectPath, "Packages", "manifest.json");
if (File.Exists(manifestPath))
{
var manifest = File.ReadAllText(manifestPath);
var packageLine = $"\"{packageId}\": \"{version}\"";
if (!manifest.Contains(packageId))
{
// Add to manifest.json
manifest = manifest.Replace("\"dependencies\": {", $"\"dependencies\": {{\n {packageLine},");
File.WriteAllText(manifestPath, manifest);
#if UNITY_EDITOR
AssetDatabase.Refresh();
#endif
}
}
await Task.CompletedTask;
}
///
/// Start MCP server
///
public async Task StartMCPServer()
{
try
{
Debug.Log("[MCP Setup] Checking for existing MCP server...");
// Don't start MCP server, just connect to existing server
bool serverExists = await CheckPortListening(8090);
if (serverExists)
{
Debug.Log("[MCP Setup] ✅ Found MCP server on port 8090 - Unity will connect as client");
mcpServerProcessId = -1; // External process so don't keep ID
return true;
}
else
{
Debug.LogWarning("[MCP Setup] ❌ No MCP server found on port 8090");
Debug.LogWarning("[MCP Setup] Please start Claude Desktop or another AI application first");
// Don't start MCP server on Unity side
return false;
}
}
catch (Exception e)
{
Debug.LogError($"[MCP Setup] Failed to start server: {e.Message}");
return false;
}
}
private async Task CheckPortListening(int port)
{
try
{
if (Application.platform == RuntimePlatform.WindowsEditor)
{
var result = await CheckCommand("netstat", $"-an | findstr :{port}");
return !string.IsNullOrEmpty(result);
}
else
{
var result = await CheckCommand("lsof", $"-i :{port}");
return !string.IsNullOrEmpty(result);
}
}
catch
{
return false;
}
}
private void SetupEnvironmentPath(ProcessStartInfo startInfo)
{
// Get current PATH
var currentPath = Environment.GetEnvironmentVariable("PATH") ?? "";
// Paths to add
var additionalPaths = new[]
{
"/opt/homebrew/bin",
"/usr/local/bin",
"/usr/bin",
"/bin"
};
var pathList = new List();
// Add additional paths first (to increase priority)
foreach (var path in additionalPaths)
{
if (Directory.Exists(path) && !pathList.Contains(path))
{
pathList.Add(path);
}
}
// Add existing PATH
if (!string.IsNullOrEmpty(currentPath))
{
var separator = Application.platform == RuntimePlatform.WindowsEditor ? ';' : ':';
foreach (var path in currentPath.Split(separator))
{
if (!string.IsNullOrEmpty(path.Trim()) && !pathList.Contains(path.Trim()))
{
pathList.Add(path.Trim());
}
}
}
// Set new PATH
var newPath = string.Join(Application.platform == RuntimePlatform.WindowsEditor ? ";" : ":", pathList);
startInfo.EnvironmentVariables["PATH"] = newPath;
Debug.Log($"[MCP Setup] Configured PATH: {newPath}");
}
///
/// Clean up existing MCP servers
///
private async Task CleanupExistingServers()
{
try
{
// First check if port is in use
bool needsCleanup = false;
for (int port = 3000; port <= 3010; port++)
{
if (await CheckPortListening(port))
{
needsCleanup = true;
Debug.Log($"[MCP Setup] Port {port} is in use");
break;
}
}
if (await CheckPortListening(8090))
{
needsCleanup = true;
Debug.Log("[MCP Setup] Port 8090 is in use");
}
if (needsCleanup)
{
Debug.Log("[MCP Setup] Cleaning up existing server processes");
await StopMCPServer();
// Wait until port is released
await Task.Delay(2000);
}
}
catch (Exception e)
{
Debug.LogWarning($"[MCP Setup] Warning during cleanup: {e.Message}");
}
}
///
/// Stop MCP server
///
public async Task StopMCPServer()
{
try
{
Debug.Log("[MCP Setup] Stopping MCP server...");
// First try to kill with saved process ID
if (mcpServerProcessId > 0)
{
try
{
var killByIdProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "/bin/kill",
Arguments = $"-9 {mcpServerProcessId}",
UseShellExecute = false,
CreateNoWindow = true
}
};
killByIdProcess.Start();
killByIdProcess.WaitForExit();
Debug.Log($"[MCP Setup] Killed process {mcpServerProcessId}");
}
catch (Exception e)
{
Debug.LogWarning($"[MCP Setup] Process ID kill error: {e.Message}");
}
}
// Kill node.js process with pkill just in case
await Task.Run(() =>
{
try
{
var killProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "/usr/bin/pkill",
Arguments = "-f \"node.*index.js\"",
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
}
};
killProcess.Start();
killProcess.WaitForExit();
Debug.Log("[MCP Setup] pkill command execution completed");
}
catch (Exception e)
{
Debug.LogWarning($"[MCP Setup] Warning during pkill execution: {e.Message}");
}
});
// Wait until process completely terminates
await Task.Delay(1000);
// Verify that port is released
bool stillRunning = await CheckPortListening(3000);
if (!stillRunning)
{
Debug.Log("[MCP Setup] MCP server stopped successfully");
return true;
}
else
{
Debug.LogWarning("[MCP Setup] MCP server may still be running");
return false;
}
}
catch (Exception e)
{
Debug.LogError($"[MCP Setup] Server stop error: {e.Message}");
return false;
}
}
}
}