Skip to content

Commit cd56dac

Browse files
committed
feat: add create scene tool
1 parent 4efa92e commit cd56dac

File tree

4 files changed

+230
-0
lines changed

4 files changed

+230
-0
lines changed

Editor/Tools/CreateSceneTool.cs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
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 and saving a new Unity scene
12+
/// </summary>
13+
public class CreateSceneTool : McpToolBase
14+
{
15+
public CreateSceneTool()
16+
{
17+
Name = "create_scene";
18+
Description = "Creates a new scene and saves it to the specified path";
19+
}
20+
21+
/// <summary>
22+
/// Execute the CreateScene 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+
// Parameters
28+
string sceneName = parameters["sceneName"]?.ToObject<string>();
29+
string folderPath = parameters["folderPath"]?.ToObject<string>();
30+
bool addToBuildSettings = parameters["addToBuildSettings"]?.ToObject<bool?>() ?? false;
31+
bool makeActive = parameters["makeActive"]?.ToObject<bool?>() ?? true;
32+
33+
if (string.IsNullOrEmpty(sceneName))
34+
{
35+
return McpUnitySocketHandler.CreateErrorResponse(
36+
"Required parameter 'sceneName' not provided",
37+
"validation_error"
38+
);
39+
}
40+
41+
// Default folder path
42+
if (string.IsNullOrEmpty(folderPath))
43+
{
44+
folderPath = "Assets";
45+
}
46+
47+
// Ensure folder exists
48+
if (!AssetDatabase.IsValidFolder(folderPath))
49+
{
50+
// Attempt to create nested folders as needed
51+
string[] parts = folderPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
52+
string current = parts.Length > 0 && parts[0] == "Assets" ? "Assets" : "Assets";
53+
for (int i = 0; i < parts.Length; i++)
54+
{
55+
if (i == 0 && parts[i] == "Assets") continue;
56+
string next = current + "/" + parts[i];
57+
if (!AssetDatabase.IsValidFolder(next))
58+
{
59+
AssetDatabase.CreateFolder(current, parts[i]);
60+
}
61+
current = next;
62+
}
63+
}
64+
65+
// Create unique path for the scene
66+
string basePath = folderPath.TrimEnd('/');
67+
string scenePath = AssetDatabase.GenerateUniqueAssetPath($"{basePath}/{sceneName}.unity");
68+
69+
try
70+
{
71+
var newScene = UnityEditor.SceneManagement.EditorSceneManager.NewScene(UnityEditor.SceneManagement.NewSceneSetup.DefaultGameObjects, UnityEditor.SceneManagement.NewSceneMode.Single);
72+
73+
bool saved = UnityEditor.SceneManagement.EditorSceneManager.SaveScene(newScene, scenePath);
74+
if (!saved)
75+
{
76+
return McpUnitySocketHandler.CreateErrorResponse(
77+
$"Failed to save scene at '{scenePath}'",
78+
"save_error"
79+
);
80+
}
81+
82+
AssetDatabase.Refresh();
83+
84+
// Make the scene active if requested
85+
if (makeActive)
86+
{
87+
UnityEditor.SceneManagement.EditorSceneManager.OpenScene(scenePath, UnityEditor.SceneManagement.OpenSceneMode.Single);
88+
}
89+
90+
// Optionally add to build settings
91+
if (addToBuildSettings)
92+
{
93+
AddSceneToBuildSettings(scenePath);
94+
}
95+
96+
McpLogger.LogInfo($"Created scene '{sceneName}' at path '{scenePath}'");
97+
98+
return new JObject
99+
{
100+
["success"] = true,
101+
["type"] = "text",
102+
["message"] = $"Successfully created scene '{sceneName}' at path '{scenePath}'",
103+
["scenePath"] = scenePath
104+
};
105+
}
106+
catch (Exception ex)
107+
{
108+
return McpUnitySocketHandler.CreateErrorResponse(
109+
$"Error creating scene: {ex.Message}",
110+
"scene_creation_error"
111+
);
112+
}
113+
}
114+
115+
private void AddSceneToBuildSettings(string scenePath)
116+
{
117+
var scenes = UnityEditor.EditorBuildSettings.scenes;
118+
119+
// Check if already present
120+
foreach (var s in scenes)
121+
{
122+
if (s.path == scenePath)
123+
{
124+
return;
125+
}
126+
}
127+
128+
var newList = new UnityEditor.EditorBuildSettingsScene[scenes.Length + 1];
129+
for (int i = 0; i < scenes.Length; i++)
130+
{
131+
newList[i] = scenes[i];
132+
}
133+
newList[newList.Length - 1] = new UnityEditor.EditorBuildSettingsScene(scenePath, true);
134+
UnityEditor.EditorBuildSettings.scenes = newList;
135+
}
136+
}
137+
}
138+
139+

Editor/UnityBridge/McpUnityServer.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,10 @@ private void RegisterTools()
253253
// Register CreatePrefabTool
254254
CreatePrefabTool createPrefabTool = new CreatePrefabTool();
255255
_tools.Add(createPrefabTool.Name, createPrefabTool);
256+
257+
// Register CreateSceneTool
258+
CreateSceneTool createSceneTool = new CreateSceneTool();
259+
_tools.Add(createSceneTool.Name, createSceneTool);
256260
}
257261

258262
/// <summary>

Server~/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
33
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
44
import { McpUnity } from './unity/mcpUnity.js';
55
import { Logger, LogLevel } from './utils/logger.js';
6+
import { registerCreateSceneTool } from './tools/createSceneTool.js';
67
import { registerMenuItemTool } from './tools/menuItemTool.js';
78
import { registerSelectGameObjectTool } from './tools/selectGameObjectTool.js';
89
import { registerAddPackageTool } from './tools/addPackageTool.js';
@@ -57,6 +58,7 @@ registerUpdateComponentTool(server, mcpUnity, toolLogger);
5758
registerAddAssetToSceneTool(server, mcpUnity, toolLogger);
5859
registerUpdateGameObjectTool(server, mcpUnity, toolLogger);
5960
registerCreatePrefabTool(server, mcpUnity, toolLogger);
61+
registerCreateSceneTool(server, mcpUnity, toolLogger);
6062

6163
// Register all resources into the MCP server
6264
registerGetTestsResource(server, mcpUnity, resourceLogger);
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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+
const toolName = "create_scene";
8+
const toolDescription =
9+
"Creates a new scene and saves it to the specified path";
10+
11+
const paramsSchema = z.object({
12+
sceneName: z
13+
.string()
14+
.describe("The name of the scene to create (without extension)"),
15+
folderPath: z
16+
.string()
17+
.optional()
18+
.describe("The folder path under 'Assets' to save into (default: Assets)"),
19+
addToBuildSettings: z
20+
.boolean()
21+
.optional()
22+
.describe("Whether to add the scene to Build Settings"),
23+
makeActive: z
24+
.boolean()
25+
.optional()
26+
.describe("Whether to open/make the new scene active after creating it"),
27+
});
28+
29+
export function registerCreateSceneTool(
30+
server: McpServer,
31+
mcpUnity: McpUnity,
32+
logger: Logger
33+
) {
34+
logger.info(`Registering tool: ${toolName}`);
35+
36+
server.tool(
37+
toolName,
38+
toolDescription,
39+
paramsSchema.shape,
40+
async (params: any) => {
41+
try {
42+
logger.info(`Executing tool: ${toolName}`, params);
43+
const result = await toolHandler(mcpUnity, params);
44+
logger.info(`Tool execution successful: ${toolName}`);
45+
return result;
46+
} catch (error) {
47+
logger.error(`Tool execution failed: ${toolName}`, error);
48+
throw error;
49+
}
50+
}
51+
);
52+
}
53+
54+
async function toolHandler(mcpUnity: McpUnity, params: any) {
55+
if (!params.sceneName) {
56+
throw new McpUnityError(
57+
ErrorType.VALIDATION,
58+
"'sceneName' must be provided"
59+
);
60+
}
61+
62+
const response = await mcpUnity.sendRequest({
63+
method: toolName,
64+
params,
65+
});
66+
67+
if (!response.success) {
68+
throw new McpUnityError(
69+
ErrorType.TOOL_EXECUTION,
70+
response.message || "Failed to create scene"
71+
);
72+
}
73+
74+
return {
75+
content: [
76+
{
77+
type: response.type,
78+
text: response.message || "Successfully created scene",
79+
},
80+
],
81+
data: {
82+
scenePath: response.scenePath,
83+
},
84+
};
85+
}

0 commit comments

Comments
 (0)