add text encoding process(base64) to 'View Script' Command

main
정희창 2025-03-26 19:11:54 +09:00
parent 8ab7d5fe2f
commit 795d63e7ac
2 changed files with 140 additions and 92 deletions

View File

@ -1,10 +1,10 @@
using UnityEngine;
using UnityEditor;
using System; using System;
using System.IO; using System.IO;
using System.Text;
using System.Linq; using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
namespace UnityMCP.Editor.Commands namespace UnityMCP.Editor.Commands
{ {
@ -18,7 +18,9 @@ namespace UnityMCP.Editor.Commands
/// </summary> /// </summary>
public static object ViewScript(JObject @params) 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; bool requireExists = (bool?)@params["require_exists"] ?? true;
// Handle path correctly to avoid double "Assets" folder issue // 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> /// <summary>
@ -70,7 +81,9 @@ namespace UnityMCP.Editor.Commands
/// </summary> /// </summary>
public static object CreateScript(JObject @params) 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 scriptType = (string)@params["script_type"] ?? "MonoBehaviour";
string namespaceName = (string)@params["namespace"]; string namespaceName = (string)@params["namespace"];
string template = (string)@params["template"]; string template = (string)@params["template"];
@ -128,7 +141,9 @@ namespace UnityMCP.Editor.Commands
string fullFilePath = Path.Combine(folderPath, scriptName); string fullFilePath = Path.Combine(folderPath, scriptName);
if (File.Exists(fullFilePath) && !overwrite) 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 try
@ -157,7 +172,9 @@ namespace UnityMCP.Editor.Commands
// Add class definition with indent based on namespace // Add class definition with indent based on namespace
string indent = string.IsNullOrEmpty(namespaceName) ? "" : " "; 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}{{"); contentBuilder.AppendLine($"{indent}{{");
// Add default Unity methods based on script type // 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} private void Start()");
contentBuilder.AppendLine($"{indent} {{"); contentBuilder.AppendLine($"{indent} {{");
contentBuilder.AppendLine($"{indent} // Initialize your component here"); contentBuilder.AppendLine(
$"{indent} // Initialize your component here"
);
contentBuilder.AppendLine($"{indent} }}"); contentBuilder.AppendLine($"{indent} }}");
contentBuilder.AppendLine(); contentBuilder.AppendLine();
contentBuilder.AppendLine($"{indent} private void Update()"); contentBuilder.AppendLine($"{indent} private void Update()");
@ -177,7 +196,9 @@ namespace UnityMCP.Editor.Commands
{ {
contentBuilder.AppendLine($"{indent} private void OnEnable()"); contentBuilder.AppendLine($"{indent} private void OnEnable()");
contentBuilder.AppendLine($"{indent} {{"); contentBuilder.AppendLine($"{indent} {{");
contentBuilder.AppendLine($"{indent} // Initialize your ScriptableObject here"); contentBuilder.AppendLine(
$"{indent} // Initialize your ScriptableObject here"
);
contentBuilder.AppendLine($"{indent} }}"); contentBuilder.AppendLine($"{indent} }}");
} }
@ -222,8 +243,12 @@ namespace UnityMCP.Editor.Commands
/// </summary> /// </summary>
public static object UpdateScript(JObject @params) public static object UpdateScript(JObject @params)
{ {
string scriptPath = (string)@params["script_path"] ?? throw new Exception("Parameter 'script_path' is required."); string scriptPath =
string content = (string)@params["content"] ?? throw new Exception("Parameter 'content' is required."); (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 createIfMissing = (bool?)@params["create_if_missing"] ?? false;
bool createFolderIfMissing = (bool?)@params["create_folder_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)) 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 // Create the file with content
@ -308,7 +335,8 @@ namespace UnityMCP.Editor.Commands
if (!Directory.Exists(fullPath)) if (!Directory.Exists(fullPath))
throw new Exception($"Folder not found: {folderPath}"); 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")) .Select(path => path.Replace(Application.dataPath, "Assets"))
.ToArray(); .ToArray();
@ -320,8 +348,12 @@ namespace UnityMCP.Editor.Commands
/// </summary> /// </summary>
public static object AttachScript(JObject @params) public static object AttachScript(JObject @params)
{ {
string objectName = (string)@params["object_name"] ?? throw new Exception("Parameter 'object_name' is required."); string objectName =
string scriptName = (string)@params["script_name"] ?? throw new Exception("Parameter 'script_name' is required."); (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 string scriptPath = (string)@params["script_path"]; // Optional
// Find the target object // Find the target object
@ -343,7 +375,11 @@ namespace UnityMCP.Editor.Commands
if (!string.IsNullOrEmpty(scriptPath)) if (!string.IsNullOrEmpty(scriptPath))
{ {
// If a specific path is provided, try that first // 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 // Use the direct path if it exists
MonoScript scriptAsset = AssetDatabase.LoadAssetAtPath<MonoScript>(scriptPath); MonoScript scriptAsset = AssetDatabase.LoadAssetAtPath<MonoScript>(scriptPath);
@ -398,8 +434,18 @@ namespace UnityMCP.Editor.Commands
// Double check the file name to avoid false matches // Double check the file name to avoid false matches
string foundFileName = Path.GetFileName(path); string foundFileName = Path.GetFileName(path);
if (!string.Equals(foundFileName, scriptFileName, StringComparison.OrdinalIgnoreCase) && if (
!string.Equals(Path.GetFileNameWithoutExtension(foundFileName), scriptNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) !string.Equals(
foundFileName,
scriptFileName,
StringComparison.OrdinalIgnoreCase
)
&& !string.Equals(
Path.GetFileNameWithoutExtension(foundFileName),
scriptNameWithoutExtension,
StringComparison.OrdinalIgnoreCase
)
)
continue; continue;
MonoScript scriptAsset = AssetDatabase.LoadAssetAtPath<MonoScript>(path); MonoScript scriptAsset = AssetDatabase.LoadAssetAtPath<MonoScript>(path);
@ -442,7 +488,9 @@ namespace UnityMCP.Editor.Commands
} }
// If we've tried all possibilities and nothing worked // 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 mcp.server.fastmcp import FastMCP, Context
from typing import List from typing import List
from unity_connection import get_unity_connection from unity_connection import get_unity_connection
import base64
def register_script_tools(mcp: FastMCP): def register_script_tools(mcp: FastMCP):
"""Register all script-related tools with the MCP server.""" """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}") print(f"ViewScript - Using normalized script path: {script_path}")
# Send command to Unity to read the script file # Send command to Unity to read the script file
response = get_unity_connection().send_command("VIEW_SCRIPT", { response = get_unity_connection().send_command(
"script_path": script_path, "VIEW_SCRIPT",
"require_exists": require_exists {"script_path": script_path, "require_exists": require_exists},
}) )
if response.get("exists", True): 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") return response.get("content", "Script contents not available")
else: else:
return response.get("message", "Script not found") return response.get("message", "Script not found")
@ -47,7 +54,7 @@ def register_script_tools(mcp: FastMCP):
template: str = None, template: str = None,
script_folder: str = None, script_folder: str = None,
overwrite: bool = False, overwrite: bool = False,
content: str = None content: str = None,
) -> str: ) -> str:
"""Create a new Unity script file. """Create a new Unity script file.
@ -98,7 +105,7 @@ def register_script_tools(mcp: FastMCP):
"script_type": script_type, "script_type": script_type,
"namespace": namespace, "namespace": namespace,
"template": template, "template": template,
"overwrite": overwrite "overwrite": overwrite,
} }
# Add script_folder if provided # Add script_folder if provided
@ -120,7 +127,7 @@ def register_script_tools(mcp: FastMCP):
script_path: str, script_path: str,
content: str, content: str,
create_if_missing: bool = False, create_if_missing: bool = False,
create_folder_if_missing: bool = False create_folder_if_missing: bool = False,
) -> str: ) -> str:
"""Update the contents of an existing Unity script. """Update the contents of an existing Unity script.
@ -157,7 +164,7 @@ def register_script_tools(mcp: FastMCP):
params = { params = {
"script_path": script_path, "script_path": script_path,
"content": content, "content": content,
"create_if_missing": True "create_if_missing": True,
} }
# Add folder creation flag if requested # Add folder creation flag if requested
@ -169,10 +176,9 @@ def register_script_tools(mcp: FastMCP):
return response.get("message", "Script updated successfully") return response.get("message", "Script updated successfully")
else: else:
# Standard update without creation flags # Standard update without creation flags
response = unity.send_command("UPDATE_SCRIPT", { response = unity.send_command(
"script_path": script_path, "UPDATE_SCRIPT", {"script_path": script_path, "content": content}
"content": content )
})
return response.get("message", "Script updated successfully") return response.get("message", "Script updated successfully")
except Exception as e: except Exception as e:
return f"Error updating script: {str(e)}" return f"Error updating script: {str(e)}"
@ -190,9 +196,9 @@ def register_script_tools(mcp: FastMCP):
""" """
try: try:
# Send command to Unity to list scripts # Send command to Unity to list scripts
response = get_unity_connection().send_command("LIST_SCRIPTS", { response = get_unity_connection().send_command(
"folder_path": folder_path "LIST_SCRIPTS", {"folder_path": folder_path}
}) )
scripts = response.get("scripts", []) scripts = response.get("scripts", [])
if not scripts: if not scripts:
return "No scripts found in the specified folder" return "No scripts found in the specified folder"
@ -202,10 +208,7 @@ def register_script_tools(mcp: FastMCP):
@mcp.tool() @mcp.tool()
def attach_script( def attach_script(
ctx: Context, ctx: Context, object_name: str, script_name: str, script_path: str = None
object_name: str,
script_name: str,
script_path: str = None
) -> str: ) -> str:
"""Attach a script component to a GameObject. """Attach a script component to a GameObject.
@ -222,9 +225,9 @@ def register_script_tools(mcp: FastMCP):
unity = get_unity_connection() unity = get_unity_connection()
# Check if the object exists # Check if the object exists
object_response = unity.send_command("FIND_OBJECTS_BY_NAME", { object_response = unity.send_command(
"name": object_name "FIND_OBJECTS_BY_NAME", {"name": object_name}
}) )
objects = object_response.get("objects", []) objects = object_response.get("objects", [])
if not objects: if not objects:
@ -235,7 +238,7 @@ def register_script_tools(mcp: FastMCP):
script_name = f"{script_name}.cs" script_name = f"{script_name}.cs"
# Remove any path information from script_name if it contains slashes # 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 # Determine the full script path if provided
if script_path is not None: if script_path is not None:
@ -251,9 +254,9 @@ def register_script_tools(mcp: FastMCP):
script_path = f"{script_path}/{script_basename}" script_path = f"{script_path}/{script_basename}"
# Check if the script is already attached # Check if the script is already attached
object_props = unity.send_command("GET_OBJECT_PROPERTIES", { object_props = unity.send_command(
"name": object_name "GET_OBJECT_PROPERTIES", {"name": object_name}
}) )
# Extract script name without .cs and without path for component type checking # Extract script name without .cs and without path for component type checking
script_class_name = script_basename.replace(".cs", "") 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}'." return f"Script '{script_class_name}' is already attached to '{object_name}'."
# Send command to Unity to attach the script # Send command to Unity to attach the script
params = { params = {"object_name": object_name, "script_name": script_basename}
"object_name": object_name,
"script_name": script_basename
}
# Add script_path if provided # Add script_path if provided
if script_path: if script_path: