Skip to content

Commit aa11e05

Browse files
committed
feat: add load scene tool
1 parent cd26e2a commit aa11e05

File tree

5 files changed

+193
-0
lines changed

5 files changed

+193
-0
lines changed

Editor/Tools/LoadSceneTool.cs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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 loading a Unity scene, optionally additively
12+
/// </summary>
13+
public class LoadSceneTool : McpToolBase
14+
{
15+
public LoadSceneTool()
16+
{
17+
Name = "load_scene";
18+
Description = "Loads a scene by path or name. Supports additive loading (default: false)";
19+
}
20+
21+
/// <summary>
22+
/// Execute the LoadScene 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+
string scenePath = parameters["scenePath"]?.ToObject<string>();
28+
string sceneName = parameters["sceneName"]?.ToObject<string>();
29+
string folderPath = parameters["folderPath"]?.ToObject<string>();
30+
bool additive = parameters["additive"]?.ToObject<bool?>() ?? false;
31+
32+
if (string.IsNullOrEmpty(scenePath))
33+
{
34+
if (string.IsNullOrEmpty(sceneName))
35+
{
36+
return McpUnitySocketHandler.CreateErrorResponse(
37+
"Provide either 'scenePath' or 'sceneName'",
38+
"validation_error"
39+
);
40+
}
41+
42+
// Resolve scene path by name (optionally within folderPath)
43+
string filter = $"{sceneName} t:Scene";
44+
string[] searchInFolders = null;
45+
if (!string.IsNullOrEmpty(folderPath))
46+
{
47+
if (!AssetDatabase.IsValidFolder(folderPath))
48+
{
49+
return McpUnitySocketHandler.CreateErrorResponse(
50+
$"Folder '{folderPath}' does not exist",
51+
"not_found_error"
52+
);
53+
}
54+
searchInFolders = new[] { folderPath };
55+
}
56+
57+
var guids = AssetDatabase.FindAssets(filter, searchInFolders);
58+
foreach (var guid in guids)
59+
{
60+
var path = AssetDatabase.GUIDToAssetPath(guid);
61+
if (System.IO.Path.GetFileNameWithoutExtension(path) == sceneName)
62+
{
63+
scenePath = path;
64+
break;
65+
}
66+
}
67+
68+
if (string.IsNullOrEmpty(scenePath))
69+
{
70+
return McpUnitySocketHandler.CreateErrorResponse(
71+
$"Scene named '{sceneName}' not found",
72+
"not_found_error"
73+
);
74+
}
75+
}
76+
77+
try
78+
{
79+
// Avoid any save prompts: save open scenes before replacing them (non-additive)
80+
if (!additive)
81+
{
82+
UnityEditor.SceneManagement.EditorSceneManager.SaveOpenScenes();
83+
}
84+
85+
var mode = additive
86+
? UnityEditor.SceneManagement.OpenSceneMode.Additive
87+
: UnityEditor.SceneManagement.OpenSceneMode.Single;
88+
89+
var openedScene = UnityEditor.SceneManagement.EditorSceneManager.OpenScene(scenePath, mode);
90+
91+
// For non-additive, scene becomes active automatically. For additive, we do not change active scene.
92+
93+
McpLogger.LogInfo($"Loaded scene at path '{scenePath}' (additive={additive})");
94+
95+
return new JObject
96+
{
97+
["success"] = true,
98+
["type"] = "text",
99+
["message"] = $"Successfully loaded scene at path '{scenePath}' (additive={additive.ToString().ToLower()})",
100+
["scenePath"] = scenePath,
101+
["additive"] = additive
102+
};
103+
}
104+
catch (Exception ex)
105+
{
106+
return McpUnitySocketHandler.CreateErrorResponse(
107+
$"Error loading scene: {ex.Message}",
108+
"scene_load_error"
109+
);
110+
}
111+
}
112+
}
113+
}
114+
115+

Editor/Tools/LoadSceneTool.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
@@ -261,6 +261,10 @@ private void RegisterTools()
261261
// Register DeleteSceneTool
262262
DeleteSceneTool deleteSceneTool = new DeleteSceneTool();
263263
_tools.Add(deleteSceneTool.Name, deleteSceneTool);
264+
265+
// Register LoadSceneTool
266+
LoadSceneTool loadSceneTool = new LoadSceneTool();
267+
_tools.Add(loadSceneTool.Name, loadSceneTool);
264268
}
265269

266270
/// <summary>

Server~/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { registerAddAssetToSceneTool } from './tools/addAssetToSceneTool.js';
1515
import { registerUpdateGameObjectTool } from './tools/updateGameObjectTool.js';
1616
import { registerCreatePrefabTool } from './tools/createPrefabTool.js';
1717
import { registerDeleteSceneTool } from './tools/deleteSceneTool.js';
18+
import { registerLoadSceneTool } from './tools/loadSceneTool.js';
1819
import { registerGetMenuItemsResource } from './resources/getMenuItemResource.js';
1920
import { registerGetConsoleLogsResource } from './resources/getConsoleLogsResource.js';
2021
import { registerGetHierarchyResource } from './resources/getScenesHierarchyResource.js';
@@ -61,6 +62,7 @@ registerUpdateGameObjectTool(server, mcpUnity, toolLogger);
6162
registerCreatePrefabTool(server, mcpUnity, toolLogger);
6263
registerCreateSceneTool(server, mcpUnity, toolLogger);
6364
registerDeleteSceneTool(server, mcpUnity, toolLogger);
65+
registerLoadSceneTool(server, mcpUnity, toolLogger);
6466

6567
// Register all resources into the MCP server
6668
registerGetTestsResource(server, mcpUnity, resourceLogger);

Server~/src/tools/loadSceneTool.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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 = 'load_scene';
8+
const toolDescription = 'Loads a scene by path or name. Supports additive loading (default: false)';
9+
10+
const paramsSchema = z.object({
11+
scenePath: z.string().optional().describe("Full asset path to the scene (e.g., 'Assets/Scenes/MyScene.unity')"),
12+
sceneName: z.string().optional().describe('Scene name without extension (used if scenePath not provided)'),
13+
folderPath: z.string().optional().describe("Optional folder scope to resolve sceneName under 'Assets'"),
14+
additive: z.boolean().optional().describe('Load additively if true; default false')
15+
});
16+
17+
export function registerLoadSceneTool(server: McpServer, mcpUnity: McpUnity, logger: Logger) {
18+
logger.info(`Registering tool: ${toolName}`);
19+
20+
server.tool(
21+
toolName,
22+
toolDescription,
23+
paramsSchema.shape,
24+
async (params: any) => {
25+
try {
26+
logger.info(`Executing tool: ${toolName}`, params);
27+
const result = await toolHandler(mcpUnity, params);
28+
logger.info(`Tool execution successful: ${toolName}`);
29+
return result;
30+
} catch (error) {
31+
logger.error(`Tool execution failed: ${toolName}`, error);
32+
throw error;
33+
}
34+
}
35+
);
36+
}
37+
38+
async function toolHandler(mcpUnity: McpUnity, params: any) {
39+
if (!params.scenePath && !params.sceneName) {
40+
throw new McpUnityError(
41+
ErrorType.VALIDATION,
42+
"Either 'scenePath' or 'sceneName' must be provided"
43+
);
44+
}
45+
46+
const response = await mcpUnity.sendRequest({
47+
method: toolName,
48+
params
49+
});
50+
51+
if (!response.success) {
52+
throw new McpUnityError(
53+
ErrorType.TOOL_EXECUTION,
54+
response.message || 'Failed to load scene'
55+
);
56+
}
57+
58+
return {
59+
content: [{
60+
type: response.type,
61+
text: response.message || 'Successfully loaded scene'
62+
}],
63+
data: {
64+
scenePath: response.scenePath,
65+
additive: response.additive
66+
}
67+
};
68+
}
69+
70+

0 commit comments

Comments
 (0)