Merge pull request #126 from xsodus/feature/vscode-ghcs-support

Feature: VSCode GitHub Copilot Support
main
Shutong Wu 2025-07-13 15:10:36 -04:00 committed by GitHub
commit 8211d85563
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 500 additions and 57 deletions

1
.gitignore vendored
View File

@ -31,3 +31,4 @@ CONTRIBUTING.md.meta
.idea/ .idea/
.vscode/ .vscode/
.aider* .aider*
.DS_Store*

View File

@ -43,6 +43,26 @@ namespace UnityMcpBridge.Editor.Data
mcpType = McpTypes.Cursor, mcpType = McpTypes.Cursor,
configStatus = "Not Configured", configStatus = "Not Configured",
}, },
new()
{
name = "VSCode GitHub Copilot",
windowsConfigPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"Code",
"User",
"settings.json"
),
linuxConfigPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
"Library",
"Application Support",
"Code",
"User",
"settings.json"
),
mcpType = McpTypes.VSCode,
configStatus = "Not Configured",
},
}; };
// Initialize status enums after construction // Initialize status enums after construction

View File

@ -4,6 +4,7 @@ namespace UnityMcpBridge.Editor.Models
{ {
ClaudeDesktop, ClaudeDesktop,
Cursor, Cursor,
VSCode,
} }
} }

View File

@ -8,13 +8,13 @@ namespace UnityMcpBridge.Editor.Windows
// Editor window to display manual configuration instructions // Editor window to display manual configuration instructions
public class ManualConfigEditorWindow : EditorWindow public class ManualConfigEditorWindow : EditorWindow
{ {
private string configPath; protected string configPath;
private string configJson; protected string configJson;
private Vector2 scrollPos; protected Vector2 scrollPos;
private bool pathCopied = false; protected bool pathCopied = false;
private bool jsonCopied = false; protected bool jsonCopied = false;
private float copyFeedbackTimer = 0; protected float copyFeedbackTimer = 0;
private McpClient mcpClient; protected McpClient mcpClient;
public static void ShowWindow(string configPath, string configJson, McpClient mcpClient) public static void ShowWindow(string configPath, string configJson, McpClient mcpClient)
{ {
@ -26,7 +26,7 @@ namespace UnityMcpBridge.Editor.Windows
window.Show(); window.Show();
} }
private void OnGUI() protected virtual void OnGUI()
{ {
scrollPos = EditorGUILayout.BeginScrollView(scrollPos); scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
@ -245,7 +245,7 @@ namespace UnityMcpBridge.Editor.Windows
EditorGUILayout.EndScrollView(); EditorGUILayout.EndScrollView();
} }
private void Update() protected virtual void Update()
{ {
// Handle the feedback message timer // Handle the feedback message timer
if (copyFeedbackTimer > 0) if (copyFeedbackTimer > 0)

View File

@ -137,6 +137,56 @@ namespace UnityMcpBridge.Editor.Windows
// Create muted button style for Manual Setup // Create muted button style for Manual Setup
GUIStyle mutedButtonStyle = new(buttonStyle); GUIStyle mutedButtonStyle = new(buttonStyle);
if (mcpClient.mcpType == McpTypes.VSCode)
{
// Special handling for VSCode GitHub Copilot
if (
GUILayout.Button(
"Auto Configure VSCode with GitHub Copilot",
buttonStyle,
GUILayout.Height(28)
)
)
{
ConfigureMcpClient(mcpClient);
}
if (GUILayout.Button("Manual Setup", mutedButtonStyle, GUILayout.Height(28)))
{
// Show VSCode specific manual setup window
string configPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? mcpClient.windowsConfigPath
: mcpClient.linuxConfigPath;
// Get the Python directory path
string pythonDir = FindPackagePythonDirectory();
// Create VSCode-specific configuration
var vscodeConfig = new
{
mcp = new
{
servers = new
{
unityMCP = new
{
command = "uv",
args = new[] { "--directory", pythonDir, "run", "server.py" }
}
}
}
};
JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented };
string manualConfigJson = JsonConvert.SerializeObject(vscodeConfig, jsonSettings);
// Use the VSCodeManualSetupWindow directly since we're in the same namespace
VSCodeManualSetupWindow.ShowWindow(configPath, manualConfigJson);
}
}
else
{
// Standard client buttons
if ( if (
GUILayout.Button( GUILayout.Button(
$"Auto Configure {mcpClient.name}", $"Auto Configure {mcpClient.name}",
@ -156,6 +206,7 @@ namespace UnityMcpBridge.Editor.Windows
: mcpClient.linuxConfigPath; : mcpClient.linuxConfigPath;
ShowManualInstructionsWindow(configPath, mcpClient); ShowManualInstructionsWindow(configPath, mcpClient);
} }
}
EditorGUILayout.Space(5); EditorGUILayout.Space(5);
EditorGUILayout.EndVertical(); EditorGUILayout.EndVertical();
@ -274,7 +325,7 @@ namespace UnityMcpBridge.Editor.Windows
isUnityBridgeRunning = !isUnityBridgeRunning; isUnityBridgeRunning = !isUnityBridgeRunning;
} }
private string WriteToConfig(string pythonDir, string configPath) private string WriteToConfig(string pythonDir, string configPath, McpClient mcpClient = null)
{ {
// Create configuration object for unityMCP // Create configuration object for unityMCP
McpConfigServer unityMCPConfig = new() McpConfigServer unityMCPConfig = new()
@ -303,14 +354,36 @@ namespace UnityMcpBridge.Editor.Windows
dynamic existingConfig = JsonConvert.DeserializeObject(existingJson); dynamic existingConfig = JsonConvert.DeserializeObject(existingJson);
existingConfig ??= new Newtonsoft.Json.Linq.JObject(); existingConfig ??= new Newtonsoft.Json.Linq.JObject();
// Handle different client types with a switch statement
switch (mcpClient?.mcpType)
{
case McpTypes.VSCode:
// VSCode specific configuration
// Ensure mcp object exists
if (existingConfig.mcp == null)
{
existingConfig.mcp = new Newtonsoft.Json.Linq.JObject();
}
// Ensure mcp.servers object exists
if (existingConfig.mcp.servers == null)
{
existingConfig.mcp.servers = new Newtonsoft.Json.Linq.JObject();
}
break;
default:
// Standard MCP configuration (Claude Desktop, Cursor, etc.)
// Ensure mcpServers object exists // Ensure mcpServers object exists
if (existingConfig.mcpServers == null) if (existingConfig.mcpServers == null)
{ {
existingConfig.mcpServers = new Newtonsoft.Json.Linq.JObject(); existingConfig.mcpServers = new Newtonsoft.Json.Linq.JObject();
} }
break;
}
// Add/update unityMCP while preserving other servers // Add/update UnityMCP server in VSCode settings
existingConfig.mcpServers.unityMCP = existingConfig.mcp.servers.unityMCP =
JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JToken>( JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JToken>(
JsonConvert.SerializeObject(unityMCPConfig) JsonConvert.SerializeObject(unityMCPConfig)
); );
@ -334,8 +407,35 @@ namespace UnityMcpBridge.Editor.Windows
{ {
// Get the Python directory path using Package Manager API // Get the Python directory path using Package Manager API
string pythonDir = FindPackagePythonDirectory(); string pythonDir = FindPackagePythonDirectory();
string manualConfigJson;
// Create the manual configuration message // Create common JsonSerializerSettings
JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented };
// Use switch statement to handle different client types
switch (mcpClient.mcpType)
{
case McpTypes.VSCode:
// Create VSCode-specific configuration with proper format
var vscodeConfig = new
{
mcp = new
{
servers = new
{
unityMCP = new
{
command = "uv",
args = new[] { "--directory", pythonDir, "run", "server.py" }
}
}
}
};
manualConfigJson = JsonConvert.SerializeObject(vscodeConfig, jsonSettings);
break;
default:
// Create standard MCP configuration for other clients
McpConfig jsonConfig = new() McpConfig jsonConfig = new()
{ {
mcpServers = new McpConfigServers mcpServers = new McpConfigServers
@ -347,9 +447,9 @@ namespace UnityMcpBridge.Editor.Windows
}, },
}, },
}; };
manualConfigJson = JsonConvert.SerializeObject(jsonConfig, jsonSettings);
JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented }; break;
string manualConfigJson = JsonConvert.SerializeObject(jsonConfig, jsonSettings); }
ManualConfigEditorWindow.ShowWindow(configPath, manualConfigJson, mcpClient); ManualConfigEditorWindow.ShowWindow(configPath, manualConfigJson, mcpClient);
} }
@ -450,7 +550,7 @@ namespace UnityMcpBridge.Editor.Windows
return "Manual Configuration Required"; return "Manual Configuration Required";
} }
string result = WriteToConfig(pythonDir, configPath); string result = WriteToConfig(pythonDir, configPath, mcpClient);
// Update the client status after successful configuration // Update the client status after successful configuration
if (result == "Configured successfully") if (result == "Configured successfully")
@ -542,18 +642,42 @@ namespace UnityMcpBridge.Editor.Windows
} }
string configJson = File.ReadAllText(configPath); string configJson = File.ReadAllText(configPath);
McpConfig config = JsonConvert.DeserializeObject<McpConfig>(configJson);
if (config?.mcpServers?.unityMCP != null)
{
string pythonDir = ServerInstaller.GetServerPath(); string pythonDir = ServerInstaller.GetServerPath();
if (
pythonDir != null // Use switch statement to handle different client types, extracting common logic
&& Array.Exists( string[] args = null;
config.mcpServers.unityMCP.args, bool configExists = false;
arg => arg.Contains(pythonDir, StringComparison.Ordinal)
) switch (mcpClient.mcpType)
) {
case McpTypes.VSCode:
dynamic config = JsonConvert.DeserializeObject(configJson);
if (config?.mcp?.servers?.unityMCP != null)
{
// Extract args from VSCode config format
args = config.mcp.servers.unityMCP.args.ToObject<string[]>();
configExists = true;
}
break;
default:
// Standard MCP configuration check for Claude Desktop, Cursor, etc.
McpConfig standardConfig = JsonConvert.DeserializeObject<McpConfig>(configJson);
if (standardConfig?.mcpServers?.unityMCP != null)
{
args = standardConfig.mcpServers.unityMCP.args;
configExists = true;
}
break;
}
// Common logic for checking configuration status
if (configExists)
{
if (pythonDir != null &&
Array.Exists(args, arg => arg.Contains(pythonDir, StringComparison.Ordinal)))
{ {
mcpClient.SetStatus(McpStatus.Configured); mcpClient.SetStatus(McpStatus.Configured);
} }

View File

@ -0,0 +1,295 @@
using System.Runtime.InteropServices;
using UnityEditor;
using UnityEngine;
using UnityMcpBridge.Editor.Models;
namespace UnityMcpBridge.Editor.Windows
{
public class VSCodeManualSetupWindow : ManualConfigEditorWindow
{
public static new void ShowWindow(string configPath, string configJson)
{
var window = GetWindow<VSCodeManualSetupWindow>("VSCode GitHub Copilot Setup");
window.configPath = configPath;
window.configJson = configJson;
window.minSize = new Vector2(550, 500);
// Create a McpClient for VSCode
window.mcpClient = new McpClient
{
name = "VSCode GitHub Copilot",
mcpType = McpTypes.VSCode
};
window.Show();
}
protected override void OnGUI()
{
scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
// Header with improved styling
EditorGUILayout.Space(10);
Rect titleRect = EditorGUILayout.GetControlRect(false, 30);
EditorGUI.DrawRect(
new Rect(titleRect.x, titleRect.y, titleRect.width, titleRect.height),
new Color(0.2f, 0.2f, 0.2f, 0.1f)
);
GUI.Label(
new Rect(titleRect.x + 10, titleRect.y + 6, titleRect.width - 20, titleRect.height),
"VSCode GitHub Copilot MCP Setup",
EditorStyles.boldLabel
);
EditorGUILayout.Space(10);
// Instructions with improved styling
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
Rect headerRect = EditorGUILayout.GetControlRect(false, 24);
EditorGUI.DrawRect(
new Rect(headerRect.x, headerRect.y, headerRect.width, headerRect.height),
new Color(0.1f, 0.1f, 0.1f, 0.2f)
);
GUI.Label(
new Rect(
headerRect.x + 8,
headerRect.y + 4,
headerRect.width - 16,
headerRect.height
),
"Setting up GitHub Copilot in VSCode with Unity MCP",
EditorStyles.boldLabel
);
EditorGUILayout.Space(10);
GUIStyle instructionStyle = new(EditorStyles.wordWrappedLabel)
{
margin = new RectOffset(10, 10, 5, 5),
};
EditorGUILayout.LabelField(
"1. Prerequisites",
EditorStyles.boldLabel
);
EditorGUILayout.LabelField(
"• Ensure you have VSCode installed",
instructionStyle
);
EditorGUILayout.LabelField(
"• Ensure you have GitHub Copilot extension installed in VSCode",
instructionStyle
);
EditorGUILayout.LabelField(
"• Ensure you have a valid GitHub Copilot subscription",
instructionStyle
);
EditorGUILayout.Space(5);
EditorGUILayout.LabelField(
"2. Steps to Configure",
EditorStyles.boldLabel
);
EditorGUILayout.LabelField(
"a) Open VSCode Settings (File > Preferences > Settings)",
instructionStyle
);
EditorGUILayout.LabelField(
"b) Click on the 'Open Settings (JSON)' button in the top right",
instructionStyle
);
EditorGUILayout.LabelField(
"c) Add the MCP configuration shown below to your settings.json file",
instructionStyle
);
EditorGUILayout.LabelField(
"d) Save the file and restart VSCode",
instructionStyle
);
EditorGUILayout.Space(5);
EditorGUILayout.LabelField(
"3. VSCode settings.json location:",
EditorStyles.boldLabel
);
// Path section with improved styling
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
string displayPath;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
displayPath = System.IO.Path.Combine(
System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData),
"Code",
"User",
"settings.json"
);
}
else
{
displayPath = System.IO.Path.Combine(
System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile),
"Library",
"Application Support",
"Code",
"User",
"settings.json"
);
}
// Store the path in the base class config path
if (string.IsNullOrEmpty(configPath))
{
configPath = displayPath;
}
// Prevent text overflow by allowing the text field to wrap
GUIStyle pathStyle = new(EditorStyles.textField) { wordWrap = true };
EditorGUILayout.TextField(
displayPath,
pathStyle,
GUILayout.Height(EditorGUIUtility.singleLineHeight)
);
// Copy button with improved styling
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUIStyle copyButtonStyle = new(GUI.skin.button)
{
padding = new RectOffset(15, 15, 5, 5),
margin = new RectOffset(10, 10, 5, 5),
};
if (
GUILayout.Button(
"Copy Path",
copyButtonStyle,
GUILayout.Height(25),
GUILayout.Width(100)
)
)
{
EditorGUIUtility.systemCopyBuffer = displayPath;
pathCopied = true;
copyFeedbackTimer = 2f;
}
if (
GUILayout.Button(
"Open File",
copyButtonStyle,
GUILayout.Height(25),
GUILayout.Width(100)
)
)
{
// Open the file using the system's default application
System.Diagnostics.Process.Start(
new System.Diagnostics.ProcessStartInfo
{
FileName = displayPath,
UseShellExecute = true,
}
);
}
if (pathCopied)
{
GUIStyle feedbackStyle = new(EditorStyles.label);
feedbackStyle.normal.textColor = Color.green;
EditorGUILayout.LabelField("Copied!", feedbackStyle, GUILayout.Width(60));
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
EditorGUILayout.Space(10);
EditorGUILayout.LabelField(
"4. Add this configuration to your settings.json:",
EditorStyles.boldLabel
);
// JSON section with improved styling
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
// Improved text area for JSON with syntax highlighting colors
GUIStyle jsonStyle = new(EditorStyles.textArea)
{
font = EditorStyles.boldFont,
wordWrap = true,
};
jsonStyle.normal.textColor = new Color(0.3f, 0.6f, 0.9f); // Syntax highlighting blue
// Draw the JSON in a text area with a taller height for better readability
EditorGUILayout.TextArea(configJson, jsonStyle, GUILayout.Height(200));
// Copy JSON button with improved styling
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (
GUILayout.Button(
"Copy JSON",
copyButtonStyle,
GUILayout.Height(25),
GUILayout.Width(100)
)
)
{
EditorGUIUtility.systemCopyBuffer = configJson;
jsonCopied = true;
copyFeedbackTimer = 2f;
}
if (jsonCopied)
{
GUIStyle feedbackStyle = new(EditorStyles.label);
feedbackStyle.normal.textColor = Color.green;
EditorGUILayout.LabelField("Copied!", feedbackStyle, GUILayout.Width(60));
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
EditorGUILayout.Space(10);
EditorGUILayout.LabelField(
"5. After configuration:",
EditorStyles.boldLabel
);
EditorGUILayout.LabelField(
"• Restart VSCode",
instructionStyle
);
EditorGUILayout.LabelField(
"• GitHub Copilot will now be able to interact with your Unity project through the MCP protocol",
instructionStyle
);
EditorGUILayout.LabelField(
"• Remember to have the Unity MCP Bridge running in Unity Editor",
instructionStyle
);
EditorGUILayout.EndVertical();
EditorGUILayout.Space(10);
// Close button at the bottom
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("Close", GUILayout.Height(30), GUILayout.Width(100)))
{
Close();
}
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndScrollView();
}
protected override void Update()
{
// Call the base implementation which handles the copy feedback timer
base.Update();
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 377fe73d52cf0435fabead5f50a0d204