Skip to content

Commit 403d1c0

Browse files
committed
added Newtonsoft dependency to the package configuration
Added the visual of the current connected clients to the Unity MCP server in the Editor Window Added option to easier config different IDEs MCP configs directly from Unity
1 parent f6bafd9 commit 403d1c0

File tree

7 files changed

+430
-153
lines changed

7 files changed

+430
-153
lines changed

.windsurfrules

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
This project is split in 2 parts:
2+
- The C# Unity Editor package in the Editor/ folder
3+
- The Node.js server in the Server/ folder.
4+
5+
Use WebSockets to communicate between the Editor and the Node.js server.
6+
For more documentation for the WebSocket library being used in Unity see https://github.com/sta/websocket-sharp/tree/master/websocket-sharp/Server
7+
8+
Minimum supported versions:
9+
- Unity 2022.3
10+
- Node.js 18.0.0
11+
12+
The MCP Node.js server implements the typescript SDK from https://github.com/modelcontextprotocol/typescript-sdk
Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
using System;
2+
using System.IO;
3+
using System.Collections.Generic;
4+
using UnityEngine;
5+
using UnityEditor;
6+
using Newtonsoft.Json;
7+
using Newtonsoft.Json.Linq;
8+
9+
namespace McpUnity.Unity
10+
{
11+
/// <summary>
12+
/// Utility class for MCP configuration operations
13+
/// </summary>
14+
public static class McpConfigUtils
15+
{
16+
/// <summary>
17+
/// Generates the MCP configuration JSON to setup the Unity MCP server in different AI Clients
18+
/// </summary>
19+
public static string GenerateMcpConfigJson(bool useTabsIndentation)
20+
{
21+
var config = new Dictionary<string, object>
22+
{
23+
{ "mcpServers", new Dictionary<string, object>
24+
{
25+
{ "mcp-unity", new Dictionary<string, object>
26+
{
27+
{ "command", "node" },
28+
{ "args", new[] { Path.Combine(GetServerPath(), "build", "index.js") } },
29+
{ "env", new Dictionary<string, string>
30+
{
31+
{ "UNITY_PORT", McpUnitySettings.Instance.Port.ToString() }
32+
}
33+
}
34+
}
35+
}
36+
}
37+
}
38+
};
39+
40+
// Initialize string writer with proper indentation
41+
var stringWriter = new StringWriter();
42+
using (var jsonWriter = new JsonTextWriter(stringWriter))
43+
{
44+
jsonWriter.Formatting = Formatting.Indented;
45+
46+
// Set indentation character and count
47+
if (useTabsIndentation)
48+
{
49+
jsonWriter.IndentChar = '\t';
50+
jsonWriter.Indentation = 1;
51+
}
52+
else
53+
{
54+
jsonWriter.IndentChar = ' ';
55+
jsonWriter.Indentation = 2;
56+
}
57+
58+
// Serialize directly to the JsonTextWriter
59+
var serializer = new JsonSerializer();
60+
serializer.Serialize(jsonWriter, config);
61+
}
62+
63+
return stringWriter.ToString().Replace("\\", "/").Replace("//", "/");
64+
}
65+
66+
/// <summary>
67+
/// Gets the absolute path to the Server directory containing package.json
68+
/// Works whether MCP Unity is installed via Package Manager or directly in the Assets folder
69+
/// </summary>
70+
public static string GetServerPath()
71+
{
72+
// First, try to find the package info via Package Manager
73+
var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath($"Packages/{McpUnitySettings.PackageName}");
74+
75+
if (packageInfo != null && !string.IsNullOrEmpty(packageInfo.resolvedPath))
76+
{
77+
return Path.Combine(packageInfo.resolvedPath, "Server");
78+
}
79+
80+
var assets = AssetDatabase.FindAssets("tsconfig");
81+
82+
if(assets.Length == 1)
83+
{
84+
// Convert relative path to absolute path
85+
var relativePath = AssetDatabase.GUIDToAssetPath(assets[0]);
86+
return Path.GetFullPath(Path.Combine(Application.dataPath, "..", relativePath));
87+
}
88+
if (assets.Length > 0)
89+
{
90+
foreach (var assetJson in assets)
91+
{
92+
string relativePath = AssetDatabase.GUIDToAssetPath(assetJson);
93+
string fullPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..", relativePath));
94+
95+
if(Path.GetFileName(Path.GetDirectoryName(fullPath)) == "Server")
96+
{
97+
return Path.GetDirectoryName(fullPath);
98+
}
99+
}
100+
}
101+
102+
// If we get here, we couldn't find the server path
103+
var errorString = "[MCP Unity] Could not locate Server directory. Please check the installation of the MCP Unity package.";
104+
105+
Debug.LogError(errorString);
106+
107+
return errorString;
108+
}
109+
110+
/// <summary>
111+
/// Adds the MCP configuration to the Windsurf MCP config file
112+
/// </summary>
113+
public static void AddToWindsurfIdeConfig(bool useTabsIndentation)
114+
{
115+
string configFilePath = GetWindsurfMcpConfigPath();
116+
AddToConfigFile(configFilePath, useTabsIndentation, "Windsurf");
117+
}
118+
119+
/// <summary>
120+
/// Adds the MCP configuration to the Claude Desktop config file
121+
/// </summary>
122+
public static void AddToClaudeDesktopConfig(bool useTabsIndentation)
123+
{
124+
string configFilePath = GetClaudeDesktopConfigPath();
125+
AddToConfigFile(configFilePath, useTabsIndentation, "Claude Desktop");
126+
}
127+
128+
/// <summary>
129+
/// Common method to add MCP configuration to a specified config file
130+
/// </summary>
131+
/// <param name="configFilePath">Path to the config file</param>
132+
/// <param name="useTabsIndentation">Whether to use tabs for indentation</param>
133+
/// <param name="productName">Name of the product (for error messages)</param>
134+
private static void AddToConfigFile(string configFilePath, bool useTabsIndentation, string productName)
135+
{
136+
try
137+
{
138+
if (string.IsNullOrEmpty(configFilePath))
139+
{
140+
EditorUtility.DisplayDialog("Error", $"{productName} config file not found. Please make sure {productName} is installed.", "OK");
141+
return;
142+
}
143+
144+
// Generate fresh MCP config JSON
145+
string mcpConfigJson = GenerateMcpConfigJson(useTabsIndentation);
146+
147+
// Parse the MCP config JSON
148+
JObject mcpConfig = JObject.Parse(mcpConfigJson);
149+
150+
// Check if the file exists
151+
if (File.Exists(configFilePath))
152+
{
153+
// Read the existing config
154+
string existingConfigJson = File.ReadAllText(configFilePath);
155+
JObject existingConfig;
156+
157+
try
158+
{
159+
existingConfig = JObject.Parse(existingConfigJson);
160+
}
161+
catch (JsonException)
162+
{
163+
// If the file exists but isn't valid JSON, create a new one
164+
existingConfig = new JObject();
165+
}
166+
167+
// Merge the mcpServers from our config into the existing config
168+
if (mcpConfig["mcpServers"] != null && mcpConfig["mcpServers"] is JObject mcpServers)
169+
{
170+
// Create mcpServers object if it doesn't exist
171+
if (existingConfig["mcpServers"] == null)
172+
{
173+
existingConfig["mcpServers"] = new JObject();
174+
}
175+
176+
// Add or update the mcp-unity server config
177+
if (mcpServers["mcp-unity"] != null)
178+
{
179+
((JObject)existingConfig["mcpServers"])["mcp-unity"] = mcpServers["mcp-unity"];
180+
}
181+
182+
// Write the updated config back to the file
183+
File.WriteAllText(configFilePath, existingConfig.ToString(Formatting.Indented));
184+
185+
EditorUtility.DisplayDialog("Success", $"MCP Unity configuration added to {productName}.", "OK");
186+
}
187+
}
188+
else if(Directory.Exists(Path.GetDirectoryName(configFilePath)))
189+
{
190+
// Create a new config file with just our config
191+
File.WriteAllText(configFilePath, mcpConfigJson);
192+
EditorUtility.DisplayDialog("Success", $"Created new {productName} config file with MCP Unity configuration.", "OK");
193+
}
194+
else
195+
{
196+
EditorUtility.DisplayDialog("Failed", $"Cannot find {productName} config file or {productName} is currently not installed. Expecting {productName} to be installed in the {configFilePath} path", "OK");
197+
}
198+
}
199+
catch (Exception ex)
200+
{
201+
EditorUtility.DisplayDialog("Error", $"Failed to add MCP configuration to {productName}: {ex.Message}", "OK");
202+
Debug.LogError($"Failed to add MCP configuration to {productName}: {ex}");
203+
}
204+
}
205+
206+
/// <summary>
207+
/// Gets the path to the Windsurf MCP config file based on the current OS
208+
/// </summary>
209+
/// <returns>The path to the Windsurf MCP config file</returns>
210+
private static string GetWindsurfMcpConfigPath()
211+
{
212+
// Base path depends on the OS
213+
string basePath;
214+
215+
if (Application.platform == RuntimePlatform.WindowsEditor)
216+
{
217+
// Windows: %USERPROFILE%/.codeium/windsurf
218+
basePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".codeium/windsurf");
219+
Debug.Log(basePath);
220+
}
221+
else if (Application.platform == RuntimePlatform.OSXEditor)
222+
{
223+
// macOS: ~/Library/Application Support/.codeium/windsurf
224+
string homeDir = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
225+
basePath = Path.Combine(homeDir, "Library", "Application Support", ".codeium/windsurf");
226+
}
227+
else
228+
{
229+
// Unsupported platform
230+
Debug.LogError("Unsupported platform for Windsurf MCP config");
231+
return null;
232+
}
233+
234+
// Return the path to the mcp_config.json file
235+
return Path.Combine(basePath, "mcp_config.json");
236+
}
237+
238+
/// <summary>
239+
/// Gets the path to the Claude Desktop config file based on the current OS
240+
/// </summary>
241+
/// <returns>The path to the Claude Desktop config file</returns>
242+
private static string GetClaudeDesktopConfigPath()
243+
{
244+
// Base path depends on the OS
245+
string basePath;
246+
247+
if (Application.platform == RuntimePlatform.WindowsEditor)
248+
{
249+
// Windows: %USERPROFILE%/AppData/Roaming/Claude
250+
basePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Claude");
251+
}
252+
else if (Application.platform == RuntimePlatform.OSXEditor)
253+
{
254+
// macOS: ~/Library/Application Support/Claude
255+
string homeDir = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
256+
basePath = Path.Combine(homeDir, "Library", "Application Support", "Claude");
257+
}
258+
else
259+
{
260+
// Unsupported platform
261+
Debug.LogError("Unsupported platform for Claude Desktop config");
262+
return null;
263+
}
264+
265+
// Return the path to the claude_desktop_config.json file
266+
return Path.Combine(basePath, "claude_desktop_config.json");
267+
}
268+
269+
/// <summary>
270+
/// Adds the MCP configuration to Cursor IDE by generating the command-line format
271+
/// </summary>
272+
public static void AddToCursorIdeConfig()
273+
{
274+
try
275+
{
276+
string serverPath = GetServerPath();
277+
string port = McpUnitySettings.Instance.Port.ToString();
278+
279+
// Generate the command-line format for Cursor IDE
280+
string cursorCommand = $"env UNITY_PORT={port} node {serverPath}/build/index.js";
281+
282+
// Copy to clipboard
283+
EditorGUIUtility.systemCopyBuffer = cursorCommand;
284+
285+
// Show instructions to the user
286+
EditorUtility.DisplayDialog(
287+
"Cursor IDE Configuration",
288+
"The Cursor IDE command has been copied to your clipboard. Please add it to Cursor IDE with these settings:\n\n" +
289+
"Name: MCP Unity\n" +
290+
"Type: command\n" +
291+
$"Command: {cursorCommand}\n\n" +
292+
"Go to Cursor → Settings → MCP → Configure and paste this command.",
293+
"OK");
294+
}
295+
catch (Exception ex)
296+
{
297+
EditorUtility.DisplayDialog("Error", $"Failed to generate Cursor IDE configuration: {ex.Message}", "OK");
298+
Debug.LogError($"Failed to generate Cursor IDE configuration: {ex}");
299+
}
300+
}
301+
}
302+
}

Editor/UnityBridge/McpConfigUtils.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)