Merge pull request #49 from flashwade03/feature/base64_encoding

add text encoding process(base64) to 'View Script' Command for fixing timeout issue.
main
Justin P Barnett 2025-03-26 12:52:27 -04:00 committed by GitHub
commit 13508f2e56
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 140 additions and 92 deletions

View File

@ -1,10 +1,10 @@
using UnityEngine;
using UnityEditor;
using System;
using System.IO;
using System.Text;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
namespace UnityMCP.Editor.Commands
{
@ -18,7 +18,9 @@ namespace UnityMCP.Editor.Commands
/// </summary>
public static object ViewScript(JObject @params)
{
string scriptPath = (string)@params["script_path"] ?? throw new Exception("Parameter 'script_path' is required.");
string scriptPath =
(string)@params["script_path"]
?? throw new Exception("Parameter 'script_path' is required.");
bool requireExists = (bool?)@params["require_exists"] ?? true;
// Handle path correctly to avoid double "Assets" folder issue
@ -47,7 +49,16 @@ namespace UnityMCP.Editor.Commands
}
}
return new { exists = true, content = File.ReadAllText(fullPath) };
string content = File.ReadAllText(fullPath);
byte[] contentBytes = System.Text.Encoding.UTF8.GetBytes(content);
string base64Content = Convert.ToBase64String(contentBytes);
return new
{
exists = true,
content = base64Content,
encoding = "base64"
};
}
/// <summary>
@ -70,7 +81,9 @@ namespace UnityMCP.Editor.Commands
/// </summary>
public static object CreateScript(JObject @params)
{
string scriptName = (string)@params["script_name"] ?? throw new Exception("Parameter 'script_name' is required.");
string scriptName =
(string)@params["script_name"]
?? throw new Exception("Parameter 'script_name' is required.");
string scriptType = (string)@params["script_type"] ?? "MonoBehaviour";
string namespaceName = (string)@params["namespace"];
string template = (string)@params["template"];
@ -128,7 +141,9 @@ namespace UnityMCP.Editor.Commands
string fullFilePath = Path.Combine(folderPath, scriptName);
if (File.Exists(fullFilePath) && !overwrite)
{
throw new Exception($"Script file '{scriptName}' already exists in '{scriptPath}' and overwrite is not enabled.");
throw new Exception(
$"Script file '{scriptName}' already exists in '{scriptPath}' and overwrite is not enabled."
);
}
try
@ -157,7 +172,9 @@ namespace UnityMCP.Editor.Commands
// Add class definition with indent based on namespace
string indent = string.IsNullOrEmpty(namespaceName) ? "" : " ";
contentBuilder.AppendLine($"{indent}public class {Path.GetFileNameWithoutExtension(scriptName)} : {scriptType}");
contentBuilder.AppendLine(
$"{indent}public class {Path.GetFileNameWithoutExtension(scriptName)} : {scriptType}"
);
contentBuilder.AppendLine($"{indent}{{");
// Add default Unity methods based on script type
@ -165,7 +182,9 @@ namespace UnityMCP.Editor.Commands
{
contentBuilder.AppendLine($"{indent} private void Start()");
contentBuilder.AppendLine($"{indent} {{");
contentBuilder.AppendLine($"{indent} // Initialize your component here");
contentBuilder.AppendLine(
$"{indent} // Initialize your component here"
);
contentBuilder.AppendLine($"{indent} }}");
contentBuilder.AppendLine();
contentBuilder.AppendLine($"{indent} private void Update()");
@ -177,7 +196,9 @@ namespace UnityMCP.Editor.Commands
{
contentBuilder.AppendLine($"{indent} private void OnEnable()");
contentBuilder.AppendLine($"{indent} {{");
contentBuilder.AppendLine($"{indent} // Initialize your ScriptableObject here");
contentBuilder.AppendLine(
$"{indent} // Initialize your ScriptableObject here"
);
contentBuilder.AppendLine($"{indent} }}");
}
@ -222,8 +243,12 @@ namespace UnityMCP.Editor.Commands
/// </summary>
public static object UpdateScript(JObject @params)
{
string scriptPath = (string)@params["script_path"] ?? throw new Exception("Parameter 'script_path' is required.");
string content = (string)@params["content"] ?? throw new Exception("Parameter 'content' is required.");
string scriptPath =
(string)@params["script_path"]
?? throw new Exception("Parameter 'script_path' is required.");
string content =
(string)@params["content"]
?? throw new Exception("Parameter 'content' is required.");
bool createIfMissing = (bool?)@params["create_if_missing"] ?? false;
bool createFolderIfMissing = (bool?)@params["create_folder_if_missing"] ?? false;
@ -257,7 +282,9 @@ namespace UnityMCP.Editor.Commands
}
else if (!Directory.Exists(directory))
{
throw new Exception($"Directory does not exist: {Path.GetDirectoryName(scriptPath)}");
throw new Exception(
$"Directory does not exist: {Path.GetDirectoryName(scriptPath)}"
);
}
// Create the file with content
@ -308,7 +335,8 @@ namespace UnityMCP.Editor.Commands
if (!Directory.Exists(fullPath))
throw new Exception($"Folder not found: {folderPath}");
string[] scripts = Directory.GetFiles(fullPath, "*.cs", SearchOption.AllDirectories)
string[] scripts = Directory
.GetFiles(fullPath, "*.cs", SearchOption.AllDirectories)
.Select(path => path.Replace(Application.dataPath, "Assets"))
.ToArray();
@ -320,8 +348,12 @@ namespace UnityMCP.Editor.Commands
/// </summary>
public static object AttachScript(JObject @params)
{
string objectName = (string)@params["object_name"] ?? throw new Exception("Parameter 'object_name' is required.");
string scriptName = (string)@params["script_name"] ?? throw new Exception("Parameter 'script_name' is required.");
string objectName =
(string)@params["object_name"]
?? throw new Exception("Parameter 'object_name' is required.");
string scriptName =
(string)@params["script_name"]
?? throw new Exception("Parameter 'script_name' is required.");
string scriptPath = (string)@params["script_path"]; // Optional
// Find the target object
@ -343,7 +375,11 @@ namespace UnityMCP.Editor.Commands
if (!string.IsNullOrEmpty(scriptPath))
{
// If a specific path is provided, try that first
if (File.Exists(Path.Combine(Application.dataPath, scriptPath.Replace("Assets/", ""))))
if (
File.Exists(
Path.Combine(Application.dataPath, scriptPath.Replace("Assets/", ""))
)
)
{
// Use the direct path if it exists
MonoScript scriptAsset = AssetDatabase.LoadAssetAtPath<MonoScript>(scriptPath);
@ -398,8 +434,18 @@ namespace UnityMCP.Editor.Commands
// Double check the file name to avoid false matches
string foundFileName = Path.GetFileName(path);
if (!string.Equals(foundFileName, scriptFileName, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(Path.GetFileNameWithoutExtension(foundFileName), scriptNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
if (
!string.Equals(
foundFileName,
scriptFileName,
StringComparison.OrdinalIgnoreCase
)
&& !string.Equals(
Path.GetFileNameWithoutExtension(foundFileName),
scriptNameWithoutExtension,
StringComparison.OrdinalIgnoreCase
)
)
continue;
MonoScript scriptAsset = AssetDatabase.LoadAssetAtPath<MonoScript>(path);
@ -442,7 +488,9 @@ namespace UnityMCP.Editor.Commands
}
// If we've tried all possibilities and nothing worked
throw new Exception($"Could not attach script '{scriptFileName}' to object '{objectName}'. No valid script found or component creation failed.");
throw new Exception(
$"Could not attach script '{scriptFileName}' to object '{objectName}'. No valid script found or component creation failed."
);
}
}
}

View File

@ -1,6 +1,8 @@
from mcp.server.fastmcp import FastMCP, Context
from typing import List
from unity_connection import get_unity_connection
import base64
def register_script_tools(mcp: FastMCP):
"""Register all script-related tools with the MCP server."""
@ -26,12 +28,17 @@ def register_script_tools(mcp: FastMCP):
print(f"ViewScript - Using normalized script path: {script_path}")
# Send command to Unity to read the script file
response = get_unity_connection().send_command("VIEW_SCRIPT", {
"script_path": script_path,
"require_exists": require_exists
})
response = get_unity_connection().send_command(
"VIEW_SCRIPT",
{"script_path": script_path, "require_exists": require_exists},
)
if response.get("exists", True):
if response.get("encoding", "base64") == "base64":
decoded_content = base64.b64decode(response.get("content")).decode(
"utf-8"
)
return decoded_content
return response.get("content", "Script contents not available")
else:
return response.get("message", "Script not found")
@ -47,7 +54,7 @@ def register_script_tools(mcp: FastMCP):
template: str = None,
script_folder: str = None,
overwrite: bool = False,
content: str = None
content: str = None,
) -> str:
"""Create a new Unity script file.
@ -98,7 +105,7 @@ def register_script_tools(mcp: FastMCP):
"script_type": script_type,
"namespace": namespace,
"template": template,
"overwrite": overwrite
"overwrite": overwrite,
}
# Add script_folder if provided
@ -120,7 +127,7 @@ def register_script_tools(mcp: FastMCP):
script_path: str,
content: str,
create_if_missing: bool = False,
create_folder_if_missing: bool = False
create_folder_if_missing: bool = False,
) -> str:
"""Update the contents of an existing Unity script.
@ -157,7 +164,7 @@ def register_script_tools(mcp: FastMCP):
params = {
"script_path": script_path,
"content": content,
"create_if_missing": True
"create_if_missing": True,
}
# Add folder creation flag if requested
@ -169,10 +176,9 @@ def register_script_tools(mcp: FastMCP):
return response.get("message", "Script updated successfully")
else:
# Standard update without creation flags
response = unity.send_command("UPDATE_SCRIPT", {
"script_path": script_path,
"content": content
})
response = unity.send_command(
"UPDATE_SCRIPT", {"script_path": script_path, "content": content}
)
return response.get("message", "Script updated successfully")
except Exception as e:
return f"Error updating script: {str(e)}"
@ -190,9 +196,9 @@ def register_script_tools(mcp: FastMCP):
"""
try:
# Send command to Unity to list scripts
response = get_unity_connection().send_command("LIST_SCRIPTS", {
"folder_path": folder_path
})
response = get_unity_connection().send_command(
"LIST_SCRIPTS", {"folder_path": folder_path}
)
scripts = response.get("scripts", [])
if not scripts:
return "No scripts found in the specified folder"
@ -202,10 +208,7 @@ def register_script_tools(mcp: FastMCP):
@mcp.tool()
def attach_script(
ctx: Context,
object_name: str,
script_name: str,
script_path: str = None
ctx: Context, object_name: str, script_name: str, script_path: str = None
) -> str:
"""Attach a script component to a GameObject.
@ -222,9 +225,9 @@ def register_script_tools(mcp: FastMCP):
unity = get_unity_connection()
# Check if the object exists
object_response = unity.send_command("FIND_OBJECTS_BY_NAME", {
"name": object_name
})
object_response = unity.send_command(
"FIND_OBJECTS_BY_NAME", {"name": object_name}
)
objects = object_response.get("objects", [])
if not objects:
@ -235,7 +238,7 @@ def register_script_tools(mcp: FastMCP):
script_name = f"{script_name}.cs"
# Remove any path information from script_name if it contains slashes
script_basename = script_name.split('/')[-1]
script_basename = script_name.split("/")[-1]
# Determine the full script path if provided
if script_path is not None:
@ -251,9 +254,9 @@ def register_script_tools(mcp: FastMCP):
script_path = f"{script_path}/{script_basename}"
# Check if the script is already attached
object_props = unity.send_command("GET_OBJECT_PROPERTIES", {
"name": object_name
})
object_props = unity.send_command(
"GET_OBJECT_PROPERTIES", {"name": object_name}
)
# Extract script name without .cs and without path for component type checking
script_class_name = script_basename.replace(".cs", "")
@ -265,10 +268,7 @@ def register_script_tools(mcp: FastMCP):
return f"Script '{script_class_name}' is already attached to '{object_name}'."
# Send command to Unity to attach the script
params = {
"object_name": object_name,
"script_name": script_basename
}
params = {"object_name": object_name, "script_name": script_basename}
# Add script_path if provided
if script_path: