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.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,19 +1,21 @@
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."""
@mcp.tool()
def view_script(ctx: Context, script_path: str, require_exists: bool = True) -> str:
"""View the contents of a Unity script file.
Args:
ctx: The MCP context
script_path: Path to the script file relative to the Assets folder
require_exists: Whether to raise an error if the file doesn't exist (default: True)
Returns:
str: The contents of the script file or error message
"""
@ -21,17 +23,22 @@ def register_script_tools(mcp: FastMCP):
# Normalize script path to ensure it has the correct format
if not script_path.startswith("Assets/"):
script_path = f"Assets/{script_path}"
# Debug to help diagnose issues
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,10 +54,10 @@ 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.
Args:
ctx: The MCP context
script_name: Name of the script (without .cs extension)
@ -60,13 +67,13 @@ def register_script_tools(mcp: FastMCP):
script_folder: Optional folder path within Assets to create the script
overwrite: Whether to overwrite if script already exists (default: False)
content: Optional custom content for the script
Returns:
str: Success message or error details
"""
try:
unity = get_unity_connection()
# Determine script path based on script_folder parameter
if script_folder:
# Use provided folder path
@ -75,13 +82,13 @@ def register_script_tools(mcp: FastMCP):
normalized_folder = script_folder
else:
normalized_folder = f"Assets/{script_folder}"
# Create the full path
if normalized_folder.endswith("/"):
script_path = f"{normalized_folder}{script_name}.cs"
else:
script_path = f"{normalized_folder}/{script_name}.cs"
# Debug to help diagnose issues
print(f"CreateScript - Folder: {script_folder}")
print(f"CreateScript - Normalized folder: {normalized_folder}")
@ -90,7 +97,7 @@ def register_script_tools(mcp: FastMCP):
# Default to Scripts folder when no folder is provided
script_path = f"Assets/Scripts/{script_name}.cs"
print(f"CreateScript - Using default script path: {script_path}")
# Send command to Unity to create the script directly
# The C# handler will handle the file existence check
params = {
@ -98,17 +105,17 @@ def register_script_tools(mcp: FastMCP):
"script_type": script_type,
"namespace": namespace,
"template": template,
"overwrite": overwrite
"overwrite": overwrite,
}
# Add script_folder if provided
if script_folder:
params["script_folder"] = script_folder
# Add content if provided
if content:
params["content"] = content
response = unity.send_command("CREATE_SCRIPT", params)
return response.get("message", "Script created successfully")
except Exception as e:
@ -120,59 +127,58 @@ 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.
Args:
ctx: The MCP context
script_path: Path to the script file relative to the Assets folder
content: New content for the script
create_if_missing: Whether to create the script if it doesn't exist (default: False)
create_folder_if_missing: Whether to create the parent directory if it doesn't exist (default: False)
Returns:
str: Success message or error details
"""
try:
unity = get_unity_connection()
# Normalize script path to ensure it has the correct format
# Make sure the path starts with Assets/ but not Assets/Assets/
if not script_path.startswith("Assets/"):
script_path = f"Assets/{script_path}"
# Debug to help diagnose issues
print(f"UpdateScript - Original path: {script_path}")
# Parse script path (for potential creation)
script_name = script_path.split("/")[-1]
if not script_name.endswith(".cs"):
script_name += ".cs"
script_path = f"{script_path}.cs"
if create_if_missing:
# When create_if_missing is true, we'll just try to update directly,
# and let Unity handle the creation if needed
params = {
"script_path": script_path,
"content": content,
"create_if_missing": True
"create_if_missing": True,
}
# Add folder creation flag if requested
if create_folder_if_missing:
params["create_folder_if_missing"] = True
# Send command to Unity to update/create the script
response = unity.send_command("UPDATE_SCRIPT", params)
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)}"
@ -180,19 +186,19 @@ def register_script_tools(mcp: FastMCP):
@mcp.tool()
def list_scripts(ctx: Context, folder_path: str = "Assets") -> str:
"""List all script files in a specified folder.
Args:
ctx: The MCP context
folder_path: Path to the folder to search (default: Assets)
Returns:
str: List of script files or error message
"""
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,79 +208,73 @@ 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.
Args:
ctx: The MCP context
object_name: Name of the target GameObject in the scene
script_name: Name of the script to attach (with or without .cs extension)
script_path: Optional full path to the script (if not in the default Scripts folder)
Returns:
str: Success message or error details
"""
try:
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:
return f"GameObject '{object_name}' not found in the scene."
# Ensure script_name has .cs extension
# Ensure script_name has .cs extension
if not script_name.lower().endswith(".cs"):
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:
# Ensure script_path starts with Assets/
if not script_path.startswith("Assets/"):
script_path = f"Assets/{script_path}"
# If path is just a directory, append the script name
if not script_path.endswith(script_basename):
if script_path.endswith("/"):
script_path = f"{script_path}{script_basename}"
else:
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", "")
# Check if component is already attached
components = object_props.get("components", [])
for component in components:
if component.get("type") == script_class_name:
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:
params["script_path"] = script_path
response = unity.send_command("ATTACH_SCRIPT", params)
return response.get("message", "Script attached successfully")
except Exception as e:
return f"Error attaching script: {str(e)}"
return f"Error attaching script: {str(e)}"