Skip to content

Commit 2dda463

Browse files
Merge pull request #73 from CoderGamester/feature/create-prefab-tool
feat: Introduce CreatePrefabTool across Unity and Node pipelines
2 parents 8c70fa7 + 9eae664 commit 2dda463

File tree

9 files changed

+286
-3
lines changed

9 files changed

+286
-3
lines changed

Editor/Tools/CreatePrefabTool.cs

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
using System;
2+
using UnityEngine;
3+
using UnityEditor;
4+
using Newtonsoft.Json.Linq;
5+
using McpUnity.Unity;
6+
using McpUnity.Utils;
7+
8+
namespace McpUnity.Tools
9+
{
10+
/// <summary>
11+
/// Tool for creating prefabs with optional MonoBehaviour scripts
12+
/// </summary>
13+
public class CreatePrefabTool : McpToolBase
14+
{
15+
public CreatePrefabTool()
16+
{
17+
Name = "create_prefab";
18+
Description = "Creates a prefab with optional MonoBehaviour script and serialized field values";
19+
}
20+
21+
/// <summary>
22+
/// Execute the CreatePrefab tool with the provided parameters
23+
/// </summary>
24+
/// <param name="parameters">Tool parameters as a JObject</param>
25+
public override JObject Execute(JObject parameters)
26+
{
27+
// Extract parameters
28+
string scriptName = parameters["scriptName"]?.ToObject<string>();
29+
string prefabName = parameters["prefabName"]?.ToObject<string>();
30+
JObject fieldValues = parameters["fieldValues"]?.ToObject<JObject>();
31+
32+
// Validate required parameters
33+
if (string.IsNullOrEmpty(prefabName))
34+
{
35+
return McpUnitySocketHandler.CreateErrorResponse(
36+
"Required parameter 'prefabName' not provided",
37+
"validation_error"
38+
);
39+
}
40+
41+
// Create a temporary GameObject
42+
GameObject tempObject = new GameObject(prefabName);
43+
44+
// Add component if provided
45+
if (!string.IsNullOrEmpty(scriptName))
46+
{
47+
try
48+
{
49+
// Add component
50+
Component component = AddComponent(tempObject, scriptName);
51+
52+
// Apply field values if provided and component exists
53+
ApplyFieldValues(fieldValues, component);
54+
}
55+
catch (Exception ex)
56+
{
57+
return McpUnitySocketHandler.CreateErrorResponse(
58+
$"Failed to add component '{scriptName}' to GameObject",
59+
"component_error"
60+
);
61+
}
62+
}
63+
64+
// For safety, we'll create a unique name if prefab already exists
65+
int counter = 1;
66+
string prefabPath = $"{prefabName}.prefab";
67+
while (AssetDatabase.AssetPathToGUID(prefabPath) != "")
68+
{
69+
prefabPath = $"{prefabName}_{counter}.prefab";
70+
counter++;
71+
}
72+
73+
// Create the prefab
74+
GameObject prefab = PrefabUtility.SaveAsPrefabAsset(tempObject, prefabPath);
75+
76+
// Clean up temporary object
77+
UnityEngine.Object.DestroyImmediate(tempObject);
78+
79+
// Refresh the asset database
80+
AssetDatabase.Refresh();
81+
82+
// Log the action
83+
McpLogger.LogInfo($"Created prefab '{prefab.name}' at path '{prefabPath}' from script '{scriptName}'");
84+
85+
// Create the response
86+
return new JObject
87+
{
88+
["success"] = true,
89+
["type"] = "text",
90+
["message"] = $"Successfully created prefab '{prefab.name}' at path '{prefabPath}'",
91+
["prefabPath"] = prefabPath
92+
};
93+
}
94+
95+
private Component AddComponent(GameObject gameObject, string scriptName)
96+
{
97+
// Find the script type
98+
Type scriptType = Type.GetType($"{scriptName}, Assembly-CSharp");
99+
if (scriptType == null)
100+
{
101+
// Try with just the class name
102+
scriptType = Type.GetType(scriptName);
103+
}
104+
105+
if (scriptType == null)
106+
{
107+
// Try to find the type using AppDomain
108+
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
109+
{
110+
scriptType = assembly.GetType(scriptName);
111+
if (scriptType != null)
112+
break;
113+
}
114+
}
115+
116+
// Throw an error if the type was not found
117+
if (scriptType == null)
118+
{
119+
return null;
120+
}
121+
122+
// Check if the type is a MonoBehaviour
123+
if (!typeof(MonoBehaviour).IsAssignableFrom(scriptType))
124+
{
125+
return null;
126+
}
127+
128+
return gameObject.AddComponent(scriptType);
129+
}
130+
131+
private void ApplyFieldValues(JObject fieldValues, Component component)
132+
{
133+
// Apply field values if provided and component exists
134+
if (fieldValues == null || fieldValues.Count == 0)
135+
{
136+
return;
137+
}
138+
139+
Undo.RecordObject(component, "Set field values");
140+
141+
foreach (var property in fieldValues.Properties())
142+
{
143+
// Get the field/property info
144+
var fieldInfo = component.GetType().GetField(property.Name,
145+
System.Reflection.BindingFlags.Public |
146+
System.Reflection.BindingFlags.NonPublic |
147+
System.Reflection.BindingFlags.Instance);
148+
149+
if (fieldInfo != null)
150+
{
151+
// Set field value
152+
object value = property.Value.ToObject(fieldInfo.FieldType);
153+
fieldInfo.SetValue(component, value);
154+
}
155+
else
156+
{
157+
// Try property
158+
var propInfo = component.GetType().GetProperty(property.Name,
159+
System.Reflection.BindingFlags.Public |
160+
System.Reflection.BindingFlags.NonPublic |
161+
System.Reflection.BindingFlags.Instance);
162+
163+
if (propInfo != null && propInfo.CanWrite)
164+
{
165+
object value = property.Value.ToObject(propInfo.PropertyType);
166+
propInfo.SetValue(component, value);
167+
}
168+
}
169+
}
170+
}
171+
}
172+
}

Editor/Tools/CreatePrefabTool.cs.meta

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

Editor/UnityBridge/McpUnityServer.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,10 @@ private void RegisterTools()
249249
// Register AddAssetToSceneTool
250250
AddAssetToSceneTool addAssetToSceneTool = new AddAssetToSceneTool();
251251
_tools.Add(addAssetToSceneTool.Name, addAssetToSceneTool);
252+
253+
// Register CreatePrefabTool
254+
CreatePrefabTool createPrefabTool = new CreatePrefabTool();
255+
_tools.Add(createPrefabTool.Name, createPrefabTool);
252256
}
253257

254258
/// <summary>

README-ja.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ MCP Unityは、Unityの`Library/PackedCache`フォルダーをワークスペー
8383
- `add_asset_to_scene`: AssetDatabaseからアセットをUnityシーンに追加
8484
> **例:** "プロジェクトからPlayerプレハブを現在のシーンに追加"
8585
86+
- `create_prefab`: プレハブを作成し、オプションでMonoBehaviourスクリプトとシリアライズされたフィールド値を設定
87+
> **例:** "'PlayerController'スクリプトから'Player'という名前のプレハブを作成"
88+
8689
### MCPサーバーリソース
8790

8891
- `unity://menu-items`: `execute_menu_item`ツールを容易にするために、Unityエディターで利用可能なすべてのメニュー項目のリストを取得

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ The following tools are available for manipulating and querying Unity scenes and
8585
- `add_asset_to_scene`: Adds an asset from the AssetDatabase to the Unity scene
8686
> **Example prompt:** "Add the Player prefab from my project to the current scene"
8787
88+
- `create_prefab`: Creates a prefab with optional MonoBehaviour script and serialized field values
89+
> **Example prompt:** "Create a prefab named 'Player' from the 'PlayerController' script"
90+
8891
### MCP Server Resources
8992

9093
- `unity://menu-items`: Retrieves a list of all available menu items in the Unity Editor to facilitate `execute_menu_item` tool

README_zh-CN.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ MCP Unity 通过将 Unity `Library/PackedCache` 文件夹添加到您的工作
8484
- `add_asset_to_scene`: 将 AssetDatabase 中的资源添加到 Unity 场景中
8585
> **示例提示:** "将我的项目中的 Player 预制体添加到当前场景"
8686
87+
- `create_prefab`: 创建预制体,并可选择添加 MonoBehaviour 脚本和设置序列化字段值
88+
> **示例提示:** "从 'PlayerController' 脚本创建一个名为 'Player' 的预制体"
89+
8790
### MCP 服务器资源
8891

8992
- `unity://menu-items`: 获取 Unity 编辑器中所有可用的菜单项列表,以方便 `execute_menu_item` 工具

Server~/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { registerGetConsoleLogsTool } from './tools/getConsoleLogsTool.js';
1212
import { registerUpdateComponentTool } from './tools/updateComponentTool.js';
1313
import { registerAddAssetToSceneTool } from './tools/addAssetToSceneTool.js';
1414
import { registerUpdateGameObjectTool } from './tools/updateGameObjectTool.js';
15+
import { registerCreatePrefabTool } from './tools/createPrefabTool.js';
1516
import { registerGetMenuItemsResource } from './resources/getMenuItemResource.js';
1617
import { registerGetConsoleLogsResource } from './resources/getConsoleLogsResource.js';
1718
import { registerGetHierarchyResource } from './resources/getScenesHierarchyResource.js';
@@ -55,6 +56,7 @@ registerGetConsoleLogsTool(server, mcpUnity, toolLogger);
5556
registerUpdateComponentTool(server, mcpUnity, toolLogger);
5657
registerAddAssetToSceneTool(server, mcpUnity, toolLogger);
5758
registerUpdateGameObjectTool(server, mcpUnity, toolLogger);
59+
registerCreatePrefabTool(server, mcpUnity, toolLogger);
5860

5961
// Register all resources into the MCP server
6062
registerGetTestsResource(server, mcpUnity, resourceLogger);

Server~/src/prompts/gameobjectHandlingPrompt.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@ export function registerGameObjectHandlingPrompt(server: McpServer) {
2020
role: 'user',
2121
content: {
2222
type: 'text',
23-
text: `You are an expert AI assistant integrated with Unity via MCP.
23+
text: `You are an expert AI assistant integrated with Unity via a MCP server.
2424
2525
When working directly with GameObjects or any of their components in Unity scenes, you have access to the following resources and tools:
2626
- Resource "get_scenes_hierarchy" (unity://scenes_hierarchy) to list all GameObjects.
2727
- Resource "get_gameobject" (unity://gameobject/{idOrName}) to fetch detailed GameObject info, with the *idOrName* being either the **instance ID**, the **name** or the **path** to the GameObject.
2828
- Tool "select_gameobject" to select a GameObject by **instance ID**, the **name** or the **path** of the GameObject.
2929
- Tool "update_gameobject" to update a GameObject's core properties (name, tag, layer, active state, static state), or create the GameObject if it does not exist.
3030
- Tool "update_component" to update or add a component on a GameObject, including common frequently used components (e.g. Transform, RectTransform, BoxCollider, Rigidbody, etc).
31+
- Tool "create_prefab" to create a prefab from a GameObject in the scene with optional MonoBehaviour script and serialized field values.
3132
3233
Workflow:
3334
1. Use "get_scenes_hierarchy" to confirm the GameObject ID, name or path for "${gameObjectIdOrName}".
@@ -38,8 +39,10 @@ Workflow:
3839
6. Confirm success and report any errors.
3940
4041
Guidance:
41-
- Use "update_gameobject" for creating GameObjects or changing their core properties.
42-
- Use "update_component" for adding or modifying components on an existing GameObject.
42+
- Use "update_gameobject" for creating GameObjects in the scene or to change a GameObject's core properties.
43+
- Use "update_component" for adding or modifying components on an existing GameObject in the scene.
44+
- Use "create_prefab" for creating prefabs from GameObjects in the scene.
45+
- Component Scripts must be compiled in the Unity project before using "update_component" or "create_prefab".
4346
- Always validate inputs and request clarification if the identifier is ambiguous.`
4447
}
4548
},
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2+
import { McpUnity } from '../unity/mcpUnity.js';
3+
import { McpUnityError, ErrorType } from '../utils/errors.js';
4+
import * as z from 'zod';
5+
import { Logger } from '../utils/logger.js';
6+
7+
// Constants for the tool
8+
const toolName = 'create_prefab';
9+
const toolDescription = 'Creates a prefab with optional MonoBehaviour script and serialized field values';
10+
11+
// Parameter schema for the tool
12+
const paramsSchema = z.object({
13+
componentName: z.string().optional().describe('The name of the MonoBehaviour Component to add to the prefab (optional)'),
14+
prefabName: z.string().describe('The name of the prefab to create'),
15+
fieldValues: z.record(z.any()).optional().describe('Optional JSON object of serialized field values to apply to the prefab')
16+
});
17+
18+
/**
19+
* Creates and registers the CreatePrefab tool with the MCP server
20+
*
21+
* @param server The MCP server to register the tool with
22+
* @param mcpUnity The McpUnity instance to communicate with Unity
23+
* @param logger The logger instance for diagnostic information
24+
*/
25+
export function registerCreatePrefabTool(server: McpServer, mcpUnity: McpUnity, logger: Logger) {
26+
logger.info(`Registering tool: ${toolName}`);
27+
28+
server.tool(
29+
toolName,
30+
toolDescription,
31+
paramsSchema.shape,
32+
async (params: any) => {
33+
try {
34+
logger.info(`Executing tool: ${toolName}`, params);
35+
const result = await toolHandler(mcpUnity, params);
36+
logger.info(`Tool execution successful: ${toolName}`);
37+
return result;
38+
} catch (error) {
39+
logger.error(`Tool execution failed: ${toolName}`, error);
40+
throw error;
41+
}
42+
}
43+
);
44+
}
45+
46+
/**
47+
* Handler function for the CreatePrefab tool
48+
*
49+
* @param mcpUnity The McpUnity instance to communicate with Unity
50+
* @param params The validated parameters for the tool
51+
* @returns A promise that resolves to the tool execution result
52+
* @throws McpUnityError if validation fails or the request to Unity fails
53+
*/
54+
async function toolHandler(mcpUnity: McpUnity, params: any) {
55+
if (!params.scriptName) {
56+
throw new McpUnityError(
57+
ErrorType.VALIDATION,
58+
"'scriptName' must be provided"
59+
);
60+
}
61+
62+
if (!params.prefabName) {
63+
throw new McpUnityError(
64+
ErrorType.VALIDATION,
65+
"'prefabName' must be provided"
66+
);
67+
}
68+
69+
const response = await mcpUnity.sendRequest({
70+
method: toolName,
71+
params
72+
});
73+
74+
if (!response.success) {
75+
throw new McpUnityError(
76+
ErrorType.TOOL_EXECUTION,
77+
response.message || `Failed to create prefab`
78+
);
79+
}
80+
81+
return {
82+
content: [{
83+
type: response.type,
84+
text: response.message || `Successfully created prefab`
85+
}],
86+
// Include the prefab path in the result for programmatic access
87+
data: {
88+
prefabPath: response.prefabPath
89+
}
90+
};
91+
}

0 commit comments

Comments
 (0)