@wonderlandengine/mcp-plugin 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/build/index.d.mts +11 -0
- package/build/index.mjs +47 -0
- package/build/mcp-server.d.ts +4 -0
- package/build/mcp-server.js +440 -0
- package/build/schemas.d.ts +121 -0
- package/build/schemas.js +149 -0
- package/build/server.d.ts +6 -0
- package/build/server.js +111 -0
- package/build/utils/bounds.d.ts +21 -0
- package/build/utils/bounds.js +144 -0
- package/build/utils/editor-api.d.ts +14 -0
- package/build/utils/editor-api.js +102 -0
- package/build/utils/pojo.d.ts +18 -0
- package/build/utils/pojo.js +87 -0
- package/build/utils/work-queue.d.ts +17 -0
- package/build/utils/work-queue.js +29 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Wonderland Editor MCP Plugin
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Wonderland Editor MCP Plugin facilitates interaction with the Model Context Protocol (MCP) within the Wonderland Engine, enhancing your development workflows with AI assistants.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
To use this plugin, you need a nightly version or version 1.4.0+ of Wonderland Engine.
|
|
10
|
+
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
### Project
|
|
14
|
+
|
|
15
|
+
First, install the plugin into your project via the "Views > Plugins > Project" view.
|
|
16
|
+
|
|
17
|
+
### AI Agent Client
|
|
18
|
+
|
|
19
|
+
Once the project is loaded and plugin execution is enabled, the MCP server runs via SSE transport on
|
|
20
|
+
`http://localhost:3020/mcp`.
|
|
21
|
+
|
|
22
|
+
If you can't use SSE, because your client doesn't support it, you can wrap it like this:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
# Wrap the mcp server via STDIO
|
|
26
|
+
npx -y supergateway --sse "http://localhost:3020/mcp"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
#### Continue
|
|
30
|
+
|
|
31
|
+
On **Windows**:
|
|
32
|
+
|
|
33
|
+
```yml
|
|
34
|
+
- name: Wonderland Editor
|
|
35
|
+
command: cmd
|
|
36
|
+
args:
|
|
37
|
+
- /c
|
|
38
|
+
- npx
|
|
39
|
+
- -y
|
|
40
|
+
- supergateway
|
|
41
|
+
- --streamableHttp
|
|
42
|
+
- http://localhost:3020/sse
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Other systems:
|
|
46
|
+
|
|
47
|
+
```yml
|
|
48
|
+
- name: Wonderland Editor
|
|
49
|
+
command: npx
|
|
50
|
+
args:
|
|
51
|
+
- -y
|
|
52
|
+
- supergateway
|
|
53
|
+
- --streamableHttp
|
|
54
|
+
- http://localhost:3020/mcp
|
|
55
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { EditorPlugin } from "@wonderlandengine/editor-api";
|
|
2
|
+
import { WorkQueue } from "./utils/work-queue.js";
|
|
3
|
+
export default class Index extends EditorPlugin {
|
|
4
|
+
queue: WorkQueue;
|
|
5
|
+
commandsCount: number;
|
|
6
|
+
status: string;
|
|
7
|
+
constructor();
|
|
8
|
+
unload(): void;
|
|
9
|
+
draw(): void;
|
|
10
|
+
update(): void;
|
|
11
|
+
}
|
package/build/index.mjs
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { EditorPlugin, ui, tools } from "@wonderlandengine/editor-api";
|
|
2
|
+
import { main, shutdown } from "./server.js";
|
|
3
|
+
import { WorkQueue } from "./utils/work-queue.js";
|
|
4
|
+
const PORT = 3020;
|
|
5
|
+
const CONFIG_EXAMPLE = ` "wonderland-editor-mcp": {
|
|
6
|
+
"url": "http://localhost:${PORT}/mcp"
|
|
7
|
+
}
|
|
8
|
+
`;
|
|
9
|
+
/* This is an example of a Wonderland Editor plugin */
|
|
10
|
+
export default class Index extends EditorPlugin {
|
|
11
|
+
queue;
|
|
12
|
+
commandsCount = 0;
|
|
13
|
+
status = "running on port " + PORT;
|
|
14
|
+
constructor() {
|
|
15
|
+
super();
|
|
16
|
+
this.name = "MCP Server";
|
|
17
|
+
this.queue = new WorkQueue();
|
|
18
|
+
main({ port: PORT, queue: this.queue }).catch((error) => {
|
|
19
|
+
this.status = "[error]" + error.toString();
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
unload() {
|
|
23
|
+
/* Shutdown server and allow rebinding the port when reloading */
|
|
24
|
+
shutdown();
|
|
25
|
+
}
|
|
26
|
+
draw() {
|
|
27
|
+
ui.text(`Status: ${this.status}`);
|
|
28
|
+
ui.text(`Commands run: ${this.commandsCount}.`);
|
|
29
|
+
ui.separator();
|
|
30
|
+
ui.text("Copy the following config into your config file\n" +
|
|
31
|
+
"(any client that supports MCP via Streamable HTTP transport):");
|
|
32
|
+
ui.text('{\n "mcpServers": {');
|
|
33
|
+
ui.inputTextMultiline("instruction", CONFIG_EXAMPLE);
|
|
34
|
+
ui.text(" }\n}\n");
|
|
35
|
+
/* Backwards compatibility for Wonderland Editor < 1.4.6 */
|
|
36
|
+
this.update();
|
|
37
|
+
}
|
|
38
|
+
update() {
|
|
39
|
+
/* Consume all functions in the queue */
|
|
40
|
+
if (!this.queue.empty) {
|
|
41
|
+
while (this.queue.pop())
|
|
42
|
+
++this.commandsCount;
|
|
43
|
+
/* Defer packaging to after the change handlers consumed the changes */
|
|
44
|
+
setTimeout(() => tools.packageProject(), 0);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { data, tools } from "@wonderlandengine/editor-api";
|
|
3
|
+
import { computeMeshBoundsSchema, computeObjectBoundsSchema, deleteObjectsSchema, importFilesSchema, importSceneFilesSchema, modifyObjectsSchema, modifyResourcesSchema, modifySettingsSchema, queryComponentTypesSchema, queryResourcesSchema, querySettingsSchema, } from "./schemas.js";
|
|
4
|
+
import { assignComponentProperties, assignNested, filterObjectProperties, } from "./utils/pojo.js";
|
|
5
|
+
import { computeTreeBounds, stringifyWithDefaults, } from "./utils/editor-api.js";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
// JS component property caches (per object-component index and per object+type fallback)
|
|
8
|
+
const jsComponentProps = new Map();
|
|
9
|
+
const jsComponentPropsByObjectType = new Map();
|
|
10
|
+
export const server = new McpServer({
|
|
11
|
+
name: "Wonderland Editor",
|
|
12
|
+
version: "0.3.0",
|
|
13
|
+
});
|
|
14
|
+
let queue = null;
|
|
15
|
+
export function setQueue(q) {
|
|
16
|
+
queue = q;
|
|
17
|
+
}
|
|
18
|
+
server.registerTool("query_resources", {
|
|
19
|
+
title: "Query Resources",
|
|
20
|
+
description: "List resources of a certain type, filtering by property, if needed.",
|
|
21
|
+
inputSchema: queryResourcesSchema,
|
|
22
|
+
}, async ({ resourceType, ids, includeFilter, excludeFilter, paths }) => {
|
|
23
|
+
try {
|
|
24
|
+
// @ts-ignore
|
|
25
|
+
const allResources = data[resourceType];
|
|
26
|
+
let resources = ids && ids.length > 0
|
|
27
|
+
? ids
|
|
28
|
+
.filter((id) => allResources.exists(id))
|
|
29
|
+
.map((id) => ({
|
|
30
|
+
id,
|
|
31
|
+
...allResources[id],
|
|
32
|
+
}))
|
|
33
|
+
: Object.entries(allResources).map(([id, res]) => ({
|
|
34
|
+
id,
|
|
35
|
+
...res,
|
|
36
|
+
}));
|
|
37
|
+
if (includeFilter && "name" in includeFilter) {
|
|
38
|
+
resources = resources.filter((res) => {
|
|
39
|
+
return res.name.indexOf(includeFilter.name) >= 0;
|
|
40
|
+
});
|
|
41
|
+
delete includeFilter.name;
|
|
42
|
+
}
|
|
43
|
+
if (includeFilter) {
|
|
44
|
+
const keys = Object.keys(includeFilter);
|
|
45
|
+
resources = resources.filter((res) => {
|
|
46
|
+
for (const key of keys) {
|
|
47
|
+
if (res[key] != includeFilter[key])
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (excludeFilter && "name" in excludeFilter) {
|
|
54
|
+
resources = resources.filter((res) => {
|
|
55
|
+
return res.name.indexOf(excludeFilter.name) >= 0;
|
|
56
|
+
});
|
|
57
|
+
delete excludeFilter.name;
|
|
58
|
+
}
|
|
59
|
+
if (excludeFilter) {
|
|
60
|
+
const keys = Object.keys(excludeFilter);
|
|
61
|
+
resources = resources.filter((res) => {
|
|
62
|
+
for (const key of keys) {
|
|
63
|
+
if (res[key] == excludeFilter[key])
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (paths && paths.length > 0) {
|
|
70
|
+
resources = resources.map(filterObjectProperties.bind(null, paths));
|
|
71
|
+
}
|
|
72
|
+
const content = resources.map((res) => ({
|
|
73
|
+
type: "text",
|
|
74
|
+
text: JSON.stringify(res),
|
|
75
|
+
}));
|
|
76
|
+
return { content };
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
const msg = (error && (error.errors || error.message)) ?? "Unknown error";
|
|
80
|
+
console.error("query_resources error:", error);
|
|
81
|
+
throw new Error("Invalid arguments: " +
|
|
82
|
+
(typeof msg === "string" ? msg : JSON.stringify(msg)));
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
server.registerTool("query_settings", {
|
|
86
|
+
title: "Query Settings",
|
|
87
|
+
description: "Retrieve the current project settings. Specify paths like 'rendering', 'physx', 'editor', 'runtime' to retrieve only specific settings.",
|
|
88
|
+
inputSchema: querySettingsSchema,
|
|
89
|
+
}, async ({ paths }) => {
|
|
90
|
+
try {
|
|
91
|
+
const settings = JSON.parse(stringifyWithDefaults(data.settings));
|
|
92
|
+
return {
|
|
93
|
+
content: [
|
|
94
|
+
{
|
|
95
|
+
type: "text",
|
|
96
|
+
text: JSON.stringify(filterObjectProperties(paths, settings)),
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
console.error("Validation errors:", error, error?.errors ?? "");
|
|
103
|
+
throw new Error("Invalid arguments: " + JSON.stringify(error.errors));
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
server.registerTool("modify_settings", {
|
|
107
|
+
title: "Modify Settings",
|
|
108
|
+
description: "Modify the project settings.",
|
|
109
|
+
inputSchema: modifySettingsSchema,
|
|
110
|
+
}, async ({ changeSettings, resetPaths }) => {
|
|
111
|
+
try {
|
|
112
|
+
let errors = [];
|
|
113
|
+
await queue.push(async () => {
|
|
114
|
+
resetPaths?.forEach((key) => {
|
|
115
|
+
const split = key.split(".");
|
|
116
|
+
try {
|
|
117
|
+
let setting = data.settings;
|
|
118
|
+
for (const k of split.slice(0, -1)) {
|
|
119
|
+
setting = setting[k];
|
|
120
|
+
if (setting === undefined || setting === null)
|
|
121
|
+
return; // nothing to delete
|
|
122
|
+
}
|
|
123
|
+
delete setting[split[split.length - 1]];
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
errors.push(`Could not reset setting at path ${key} - not found.`);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
if (changeSettings) {
|
|
130
|
+
assignNested(data.settings, changeSettings);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
content: [
|
|
135
|
+
{
|
|
136
|
+
type: "text",
|
|
137
|
+
text: "Done.",
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
const msg = (error && (error.errors || error.message)) ?? "Unknown error";
|
|
144
|
+
console.error("Validation errors:", error, error?.errors ?? "");
|
|
145
|
+
throw new Error("Invalid arguments: " +
|
|
146
|
+
(typeof msg === "string" ? msg : JSON.stringify(msg)));
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
if ('getComponentTypes' in tools)
|
|
150
|
+
server.registerTool("query_component_types", {
|
|
151
|
+
title: "Query Component Types",
|
|
152
|
+
description: "Retrieve the current registered component types and their property definitions.",
|
|
153
|
+
inputSchema: queryComponentTypesSchema,
|
|
154
|
+
}, async ({ names }) => {
|
|
155
|
+
try {
|
|
156
|
+
const settings = JSON.parse(stringifyWithDefaults(data.settings));
|
|
157
|
+
const componentTypes = tools.getComponentTypes();
|
|
158
|
+
let filteredComponentTypes = componentTypes;
|
|
159
|
+
if (names && names.length > 0) {
|
|
160
|
+
filteredComponentTypes = componentTypes.filter(ct => names.includes(ct.name));
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
content: [
|
|
164
|
+
{
|
|
165
|
+
type: "text",
|
|
166
|
+
text: JSON.stringify(filteredComponentTypes),
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
console.error("Validation errors:", error, error?.errors ?? "");
|
|
173
|
+
throw new Error("Invalid arguments: " + JSON.stringify(error.errors));
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
server.registerTool("delete_objects", {
|
|
177
|
+
title: "Delete Objects",
|
|
178
|
+
description: "Delete objects.",
|
|
179
|
+
inputSchema: deleteObjectsSchema,
|
|
180
|
+
}, async ({ objectIds }) => {
|
|
181
|
+
try {
|
|
182
|
+
await queue.push(async () => {
|
|
183
|
+
objectIds.map((objectId) => {
|
|
184
|
+
delete data.objects[objectId];
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
return {
|
|
188
|
+
content: [{ type: "text", text: "Done." }],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
const msg = (error && (error.errors || error.message)) ?? "Unknown error";
|
|
193
|
+
console.error("Validation errors:", error, error?.errors ?? "");
|
|
194
|
+
throw new Error("Invalid arguments: " +
|
|
195
|
+
(typeof msg === "string" ? msg : JSON.stringify(msg)));
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
server.registerTool("modify_objects", {
|
|
199
|
+
title: "Modify Objects",
|
|
200
|
+
description: "Create or modify objects in the Wonderland Engine project. " +
|
|
201
|
+
"Properties not specified in modifications are kept as-is. " +
|
|
202
|
+
"If an id is not used, the object will be created.",
|
|
203
|
+
inputSchema: modifyObjectsSchema,
|
|
204
|
+
}, async ({ modifications }) => {
|
|
205
|
+
try {
|
|
206
|
+
await queue.push(async () => {
|
|
207
|
+
modifications.map(({ parentId, position, rotation, scaling, id, name, addComponents, modifyComponents, removeComponents, }) => {
|
|
208
|
+
const o = id
|
|
209
|
+
? data.objects[id]
|
|
210
|
+
: data.objects.add();
|
|
211
|
+
if (!o)
|
|
212
|
+
throw new Error(`Object with id ${id} not found.`);
|
|
213
|
+
if (name)
|
|
214
|
+
o.name = name;
|
|
215
|
+
if (parentId)
|
|
216
|
+
o.parent = parentId;
|
|
217
|
+
if (position)
|
|
218
|
+
o.translation = position;
|
|
219
|
+
if (rotation)
|
|
220
|
+
o.rotation = rotation;
|
|
221
|
+
if (scaling)
|
|
222
|
+
o.scaling = scaling;
|
|
223
|
+
if (modifyComponents) {
|
|
224
|
+
modifyComponents.forEach(({ index, properties }) => {
|
|
225
|
+
const comp = o.components[index];
|
|
226
|
+
if (comp.type == null)
|
|
227
|
+
return;
|
|
228
|
+
if (!properties)
|
|
229
|
+
return;
|
|
230
|
+
// @ts-ignore
|
|
231
|
+
const props = comp[comp.type];
|
|
232
|
+
assignComponentProperties(comp.type, props, properties, o, index);
|
|
233
|
+
for (const key of Object.keys(properties)) {
|
|
234
|
+
try {
|
|
235
|
+
// @ts-ignore
|
|
236
|
+
comp[o.components[index].type][key] = properties[key];
|
|
237
|
+
}
|
|
238
|
+
catch (e) {
|
|
239
|
+
console.error("Could not assign property", key, "to", comp.type, "component at index", index, "of object", o.name, "\n", e);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
if (removeComponents) {
|
|
245
|
+
removeComponents
|
|
246
|
+
.sort()
|
|
247
|
+
.reverse()
|
|
248
|
+
.forEach((i) => delete o.components[i]);
|
|
249
|
+
}
|
|
250
|
+
if (addComponents) {
|
|
251
|
+
addComponents.forEach((c) => {
|
|
252
|
+
const index = o.components?.length ?? 0;
|
|
253
|
+
o.components[index].type = c.type;
|
|
254
|
+
// @ts-ignore
|
|
255
|
+
const comp = o.components[index][c.type];
|
|
256
|
+
if (c.properties)
|
|
257
|
+
assignComponentProperties(c.type, comp, c.properties, o, index);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
return { content: [{ type: "text", text: "Done." }] };
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
const msg = (error && (error.errors || error.message)) ?? "Unknown error";
|
|
266
|
+
console.error("modify_objects error:", error);
|
|
267
|
+
throw new Error("Invalid arguments: " +
|
|
268
|
+
(typeof msg === "string" ? msg : JSON.stringify(msg)));
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
server.registerTool("modify_resources", {
|
|
272
|
+
title: "Modify Resources",
|
|
273
|
+
description: "Create or modify resources in the Wonderland Engine project. " +
|
|
274
|
+
"Cannot be used to modify objects. Use modify_objects to modify " +
|
|
275
|
+
"objects. Properties not specified in changeProperties or resetProperties are kept as-is.",
|
|
276
|
+
inputSchema: modifyResourcesSchema,
|
|
277
|
+
}, async ({ modifications }) => {
|
|
278
|
+
try {
|
|
279
|
+
await queue.push(async () => {
|
|
280
|
+
modifications.map(({ id, resourceType, changeProperties, resetProperties }) => {
|
|
281
|
+
// @ts-ignore
|
|
282
|
+
const res = id ? data[resourceType][id] : data[resourceType].add();
|
|
283
|
+
if (resetProperties) {
|
|
284
|
+
resetProperties.forEach((key) => {
|
|
285
|
+
delete res[key];
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
if (changeProperties) {
|
|
289
|
+
assignNested(res, changeProperties);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
return { content: [{ type: "text", text: "Done." }] };
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
const msg = (error && (error.errors || error.message)) ?? "Unknown error";
|
|
297
|
+
console.error("Validation errors:", error, error?.errors ?? "");
|
|
298
|
+
throw new Error("Invalid arguments: " +
|
|
299
|
+
(typeof msg === "string" ? msg : JSON.stringify(msg)));
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
server.registerTool("import_scenes", {
|
|
303
|
+
title: "Import Scenes",
|
|
304
|
+
description: "Import scene files (e.g. GLB, FBX, OBJ) by path. Try to batch calls to this function.",
|
|
305
|
+
inputSchema: importSceneFilesSchema,
|
|
306
|
+
}, async ({ imports }) => {
|
|
307
|
+
try {
|
|
308
|
+
const results = [];
|
|
309
|
+
await queue.push(async () => {
|
|
310
|
+
// For each import, create a new container object (named after the file basename),
|
|
311
|
+
// import the scene under it, then report the immediate children with their tree bounds.
|
|
312
|
+
for (const entry of imports) {
|
|
313
|
+
const scenePath = entry?.path;
|
|
314
|
+
const allObjects = data.objects;
|
|
315
|
+
const baseName = path.basename(scenePath);
|
|
316
|
+
// @ts-ignore
|
|
317
|
+
const containerObject = data.objects.add();
|
|
318
|
+
containerObject.name = baseName;
|
|
319
|
+
// Snapshot existing object IDs to detect new ones created by this import
|
|
320
|
+
const beforeIds = new Set(Object.keys(allObjects));
|
|
321
|
+
// Import under the created container
|
|
322
|
+
await tools.loadScene(scenePath, {
|
|
323
|
+
parent: containerObject.resourceKey,
|
|
324
|
+
});
|
|
325
|
+
// Compute set of new object IDs
|
|
326
|
+
const newIds = new Set();
|
|
327
|
+
for (const id of Object.keys(allObjects))
|
|
328
|
+
if (!beforeIds.has(id))
|
|
329
|
+
newIds.add(id);
|
|
330
|
+
// Ensure all newly created root objects are parented to the container
|
|
331
|
+
// Some importers may ignore the parent option. If a new object has no parent
|
|
332
|
+
// or its parent is not another new object, treat it as a root and reparent it.
|
|
333
|
+
for (const id of newIds) {
|
|
334
|
+
const p = allObjects[id]?.parent;
|
|
335
|
+
const parentInNewSet = p != null && newIds.has(String(p));
|
|
336
|
+
if (p == null || !parentInNewSet) {
|
|
337
|
+
allObjects[id].parent = containerObject.resourceKey;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// Imported roots are the newly created objects that are direct children of the container
|
|
341
|
+
const importedRootIds = [];
|
|
342
|
+
for (const id of newIds) {
|
|
343
|
+
const p = allObjects[id]?.parent;
|
|
344
|
+
if (p != null &&
|
|
345
|
+
String(p) === String(containerObject.resourceKey)) {
|
|
346
|
+
importedRootIds.push(id);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
for (const oid of importedRootIds) {
|
|
350
|
+
const { bounds: b, count } = computeTreeBounds(oid, data.objects);
|
|
351
|
+
results.push({
|
|
352
|
+
id: oid,
|
|
353
|
+
rootId: oid,
|
|
354
|
+
containerId: containerObject.resourceKey,
|
|
355
|
+
bounds: b && { min: [b[0], b[1], b[2]], max: [b[3], b[4], b[5]] },
|
|
356
|
+
count,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
return {
|
|
362
|
+
content: results.map((r) => ({
|
|
363
|
+
type: "text",
|
|
364
|
+
text: JSON.stringify(r),
|
|
365
|
+
})),
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
const msg = (error && (error.errors || error.message)) ?? "Unknown error";
|
|
370
|
+
console.error("[import_scenes] Error:", error);
|
|
371
|
+
throw new Error("Invalid arguments: " +
|
|
372
|
+
(typeof msg === "string" ? msg : JSON.stringify(msg)));
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
server.registerTool("import_files", {
|
|
376
|
+
title: "Import Files",
|
|
377
|
+
description: "Import files (e.g. images) by path.",
|
|
378
|
+
inputSchema: importFilesSchema,
|
|
379
|
+
}, async ({ imports }) => {
|
|
380
|
+
try {
|
|
381
|
+
await queue.push(async () => {
|
|
382
|
+
imports.map((path) => tools.loadFile(path));
|
|
383
|
+
});
|
|
384
|
+
return { content: [{ type: "text", text: "Done." }] };
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
const msg = (error && (error.errors || error.message)) ?? "Unknown error";
|
|
388
|
+
console.error("Validation errors:", error, error?.errors ?? "");
|
|
389
|
+
throw new Error("Invalid arguments: " +
|
|
390
|
+
(typeof msg === "string" ? msg : JSON.stringify(msg)));
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
server.registerTool("compute_mesh_bounds", {
|
|
394
|
+
title: "Compute Mesh Bounds",
|
|
395
|
+
description: "Compute the bounds of the given mesh resources.",
|
|
396
|
+
inputSchema: computeMeshBoundsSchema,
|
|
397
|
+
}, async ({ meshIds }) => {
|
|
398
|
+
try {
|
|
399
|
+
const bounds = meshIds.map((meshId) => {
|
|
400
|
+
const out = new Float32Array(6);
|
|
401
|
+
tools.computeMeshBounds(meshId, out);
|
|
402
|
+
return { min: out.subarray(0, 3), max: out.subarray(3, 6) };
|
|
403
|
+
});
|
|
404
|
+
return {
|
|
405
|
+
content: bounds.map((b) => ({ type: "text", text: JSON.stringify(b) })),
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
const msg = (error && (error.errors || error.message)) ?? "Unknown error";
|
|
410
|
+
console.error("Validation errors:", error, error?.errors ?? "");
|
|
411
|
+
throw new Error("Invalid arguments: " +
|
|
412
|
+
(typeof msg === "string" ? msg : JSON.stringify(msg)));
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
server.registerTool("compute_object_bounds", {
|
|
416
|
+
title: "Compute Object Bounds",
|
|
417
|
+
description: "Compute aggregated subtree bounds for the given object IDs (including children).",
|
|
418
|
+
inputSchema: computeObjectBoundsSchema,
|
|
419
|
+
}, async ({ objectIds }) => {
|
|
420
|
+
try {
|
|
421
|
+
const out = objectIds.map((oid) => {
|
|
422
|
+
const { bounds: b, count } = computeTreeBounds(oid, data.objects);
|
|
423
|
+
if (b) {
|
|
424
|
+
const minOut = [b[0], b[1], b[2]];
|
|
425
|
+
const maxOut = [b[3], b[4], b[5]];
|
|
426
|
+
return { id: oid, count, bounds: { min: minOut, max: maxOut } };
|
|
427
|
+
}
|
|
428
|
+
return { id: oid, count, bounds: null };
|
|
429
|
+
});
|
|
430
|
+
return {
|
|
431
|
+
content: out.map((e) => ({ type: "text", text: JSON.stringify(e) })),
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
const msg = (error && (error.errors || error.message)) ?? "Unknown error";
|
|
436
|
+
console.error("Validation errors:", error, error?.errors ?? "");
|
|
437
|
+
throw new Error("Invalid arguments: " +
|
|
438
|
+
(typeof msg === "string" ? msg : JSON.stringify(msg)));
|
|
439
|
+
}
|
|
440
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const ResourceTypes: readonly ["objects", "textures", "meshes", "materials", "animations", "skins", "images", "shaders", "pipelines", "fonts", "morphTargets", "particleEffects"];
|
|
3
|
+
export declare const queryResourcesSchema: {
|
|
4
|
+
resourceType: z.ZodEnum<["objects", "textures", "meshes", "materials", "animations", "skins", "images", "shaders", "pipelines", "fonts", "morphTargets", "particleEffects"]>;
|
|
5
|
+
ids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
6
|
+
includeFilter: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
7
|
+
excludeFilter: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
8
|
+
paths: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
9
|
+
};
|
|
10
|
+
export declare const querySettingsSchema: {
|
|
11
|
+
paths: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
12
|
+
};
|
|
13
|
+
export declare const modifySettingsSchema: {
|
|
14
|
+
changeSettings: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
15
|
+
resetPaths: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
16
|
+
};
|
|
17
|
+
export declare const queryComponentTypesSchema: {
|
|
18
|
+
names: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
19
|
+
};
|
|
20
|
+
export declare const deleteObjectsSchema: {
|
|
21
|
+
objectIds: z.ZodArray<z.ZodString, "many">;
|
|
22
|
+
};
|
|
23
|
+
export declare const modifyObjectsSchema: {
|
|
24
|
+
modifications: z.ZodArray<z.ZodObject<{
|
|
25
|
+
name: z.ZodOptional<z.ZodString>;
|
|
26
|
+
id: z.ZodOptional<z.ZodString>;
|
|
27
|
+
parentId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
28
|
+
position: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
29
|
+
rotation: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
30
|
+
scaling: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
31
|
+
addComponents: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
32
|
+
type: z.ZodString;
|
|
33
|
+
properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
34
|
+
}, "strip", z.ZodTypeAny, {
|
|
35
|
+
type: string;
|
|
36
|
+
properties?: Record<string, any> | undefined;
|
|
37
|
+
}, {
|
|
38
|
+
type: string;
|
|
39
|
+
properties?: Record<string, any> | undefined;
|
|
40
|
+
}>, "many">>;
|
|
41
|
+
modifyComponents: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
42
|
+
index: z.ZodNumber;
|
|
43
|
+
properties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
44
|
+
}, "strip", z.ZodTypeAny, {
|
|
45
|
+
index: number;
|
|
46
|
+
properties?: Record<string, any> | undefined;
|
|
47
|
+
}, {
|
|
48
|
+
index: number;
|
|
49
|
+
properties?: Record<string, any> | undefined;
|
|
50
|
+
}>, "many">>;
|
|
51
|
+
removeComponents: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
|
|
52
|
+
}, "strip", z.ZodTypeAny, {
|
|
53
|
+
name?: string | undefined;
|
|
54
|
+
id?: string | undefined;
|
|
55
|
+
parentId?: string | null | undefined;
|
|
56
|
+
position?: number[] | undefined;
|
|
57
|
+
rotation?: number[] | undefined;
|
|
58
|
+
scaling?: number[] | undefined;
|
|
59
|
+
addComponents?: {
|
|
60
|
+
type: string;
|
|
61
|
+
properties?: Record<string, any> | undefined;
|
|
62
|
+
}[] | undefined;
|
|
63
|
+
modifyComponents?: {
|
|
64
|
+
index: number;
|
|
65
|
+
properties?: Record<string, any> | undefined;
|
|
66
|
+
}[] | undefined;
|
|
67
|
+
removeComponents?: number[] | undefined;
|
|
68
|
+
}, {
|
|
69
|
+
name?: string | undefined;
|
|
70
|
+
id?: string | undefined;
|
|
71
|
+
parentId?: string | null | undefined;
|
|
72
|
+
position?: number[] | undefined;
|
|
73
|
+
rotation?: number[] | undefined;
|
|
74
|
+
scaling?: number[] | undefined;
|
|
75
|
+
addComponents?: {
|
|
76
|
+
type: string;
|
|
77
|
+
properties?: Record<string, any> | undefined;
|
|
78
|
+
}[] | undefined;
|
|
79
|
+
modifyComponents?: {
|
|
80
|
+
index: number;
|
|
81
|
+
properties?: Record<string, any> | undefined;
|
|
82
|
+
}[] | undefined;
|
|
83
|
+
removeComponents?: number[] | undefined;
|
|
84
|
+
}>, "many">;
|
|
85
|
+
};
|
|
86
|
+
export declare const modifyResourcesSchema: {
|
|
87
|
+
modifications: z.ZodArray<z.ZodObject<{
|
|
88
|
+
resourceType: z.ZodEnum<["objects", "textures", "meshes", "materials", "animations", "skins", "images", "shaders", "pipelines", "fonts", "morphTargets", "particleEffects"]>;
|
|
89
|
+
id: z.ZodOptional<z.ZodString>;
|
|
90
|
+
changeProperties: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
91
|
+
resetProperties: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
92
|
+
}, "strip", z.ZodTypeAny, {
|
|
93
|
+
resourceType: "objects" | "textures" | "meshes" | "materials" | "animations" | "skins" | "images" | "shaders" | "pipelines" | "fonts" | "morphTargets" | "particleEffects";
|
|
94
|
+
id?: string | undefined;
|
|
95
|
+
changeProperties?: Record<string, any> | undefined;
|
|
96
|
+
resetProperties?: string[] | undefined;
|
|
97
|
+
}, {
|
|
98
|
+
resourceType: "objects" | "textures" | "meshes" | "materials" | "animations" | "skins" | "images" | "shaders" | "pipelines" | "fonts" | "morphTargets" | "particleEffects";
|
|
99
|
+
id?: string | undefined;
|
|
100
|
+
changeProperties?: Record<string, any> | undefined;
|
|
101
|
+
resetProperties?: string[] | undefined;
|
|
102
|
+
}>, "many">;
|
|
103
|
+
};
|
|
104
|
+
export declare const importSceneFilesSchema: {
|
|
105
|
+
imports: z.ZodArray<z.ZodObject<{
|
|
106
|
+
path: z.ZodString;
|
|
107
|
+
}, "strip", z.ZodTypeAny, {
|
|
108
|
+
path: string;
|
|
109
|
+
}, {
|
|
110
|
+
path: string;
|
|
111
|
+
}>, "many">;
|
|
112
|
+
};
|
|
113
|
+
export declare const importFilesSchema: {
|
|
114
|
+
imports: z.ZodArray<z.ZodString, "many">;
|
|
115
|
+
};
|
|
116
|
+
export declare const computeMeshBoundsSchema: {
|
|
117
|
+
meshIds: z.ZodArray<z.ZodString, "many">;
|
|
118
|
+
};
|
|
119
|
+
export declare const computeObjectBoundsSchema: {
|
|
120
|
+
objectIds: z.ZodArray<z.ZodString, "many">;
|
|
121
|
+
};
|