godot-mcp-runtime 2.3.0 → 3.1.0

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.
Files changed (59) hide show
  1. package/README.md +46 -132
  2. package/dist/dispatch.d.ts +1 -11
  3. package/dist/dispatch.d.ts.map +1 -1
  4. package/dist/dispatch.js +32 -33
  5. package/dist/dispatch.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +12 -10
  9. package/dist/index.js.map +1 -1
  10. package/dist/scripts/godot_operations.gd +268 -382
  11. package/dist/scripts/mcp_bridge.gd +206 -44
  12. package/dist/tools/autoload-tools.d.ts +51 -0
  13. package/dist/tools/autoload-tools.d.ts.map +1 -0
  14. package/dist/tools/autoload-tools.js +191 -0
  15. package/dist/tools/autoload-tools.js.map +1 -0
  16. package/dist/tools/node-tools.d.ts +9 -78
  17. package/dist/tools/node-tools.d.ts.map +1 -1
  18. package/dist/tools/node-tools.js +188 -312
  19. package/dist/tools/node-tools.js.map +1 -1
  20. package/dist/tools/project-tools.d.ts +0 -168
  21. package/dist/tools/project-tools.d.ts.map +1 -1
  22. package/dist/tools/project-tools.js +191 -1240
  23. package/dist/tools/project-tools.js.map +1 -1
  24. package/dist/tools/runtime-tools.d.ts +108 -0
  25. package/dist/tools/runtime-tools.d.ts.map +1 -0
  26. package/dist/tools/runtime-tools.js +994 -0
  27. package/dist/tools/runtime-tools.js.map +1 -0
  28. package/dist/tools/scene-tools.d.ts +6 -48
  29. package/dist/tools/scene-tools.d.ts.map +1 -1
  30. package/dist/tools/scene-tools.js +76 -212
  31. package/dist/tools/scene-tools.js.map +1 -1
  32. package/dist/tools/validate-tools.d.ts.map +1 -1
  33. package/dist/tools/validate-tools.js +115 -51
  34. package/dist/tools/validate-tools.js.map +1 -1
  35. package/dist/utils/autoload-ini.d.ts +38 -0
  36. package/dist/utils/autoload-ini.d.ts.map +1 -0
  37. package/dist/utils/autoload-ini.js +124 -0
  38. package/dist/utils/autoload-ini.js.map +1 -0
  39. package/dist/utils/bridge-manager.d.ts +46 -0
  40. package/dist/utils/bridge-manager.d.ts.map +1 -0
  41. package/dist/utils/bridge-manager.js +186 -0
  42. package/dist/utils/bridge-manager.js.map +1 -0
  43. package/dist/utils/bridge-protocol.d.ts +37 -0
  44. package/dist/utils/bridge-protocol.d.ts.map +1 -0
  45. package/dist/utils/bridge-protocol.js +78 -0
  46. package/dist/utils/bridge-protocol.js.map +1 -0
  47. package/dist/utils/godot-runner.d.ts +102 -16
  48. package/dist/utils/godot-runner.d.ts.map +1 -1
  49. package/dist/utils/godot-runner.js +497 -284
  50. package/dist/utils/godot-runner.js.map +1 -1
  51. package/dist/utils/handler-helpers.d.ts +34 -0
  52. package/dist/utils/handler-helpers.d.ts.map +1 -0
  53. package/dist/utils/handler-helpers.js +55 -0
  54. package/dist/utils/handler-helpers.js.map +1 -0
  55. package/dist/utils/logger.d.ts +4 -0
  56. package/dist/utils/logger.d.ts.map +1 -0
  57. package/dist/utils/logger.js +11 -0
  58. package/dist/utils/logger.js.map +1 -0
  59. package/package.json +8 -4
@@ -1,11 +1,12 @@
1
1
  import { existsSync } from 'fs';
2
2
  import { join } from 'path';
3
- import { normalizeParameters, convertCamelToSnakeCase, validatePath, createErrorResponse, extractGdError, validateSceneArgs, } from '../utils/godot-runner.js';
3
+ import { normalizeParameters, validateSubPath, validateNodePath, createErrorResponse, validateSceneArgs, } from '../utils/godot-runner.js';
4
+ import { executeSceneOp } from '../utils/handler-helpers.js';
4
5
  // --- Tool definitions ---
5
6
  export const nodeToolDefinitions = [
6
7
  {
7
- name: 'delete_node',
8
- description: 'Remove a node and all its children from a Godot scene file. Use when pruning the scene tree; for replacing a node in place, pair with add_node. Saves automatically. Cannot delete the scene root. Errors if nodePath does not exist. Returns { success, nodePath }.',
8
+ name: 'delete_nodes',
9
+ description: 'Remove one or more nodes (and their descendants) from a scene file. Always-array: pass a single-element nodePaths array for one-off deletes. Saves once at the end. Cannot delete the scene root that entry returns an error and the rest still process. Returns: results array with one entry per nodePath in input order (success or error message).',
9
10
  annotations: { destructiveHint: true },
10
11
  inputSchema: {
11
12
  type: 'object',
@@ -15,36 +16,34 @@ export const nodeToolDefinitions = [
15
16
  type: 'string',
16
17
  description: 'Scene file path relative to the project (e.g. "scenes/main.tscn")',
17
18
  },
18
- nodePath: {
19
- type: 'string',
20
- description: 'Node path from scene root (e.g. "root/Player/Sprite2D")',
19
+ nodePaths: {
20
+ type: 'array',
21
+ items: { type: 'string' },
22
+ description: 'Node paths from scene root to delete (e.g. ["root/Player/Sprite2D"])',
21
23
  },
22
24
  },
23
- required: ['projectPath', 'scenePath', 'nodePath'],
25
+ required: ['projectPath', 'scenePath', 'nodePaths'],
24
26
  },
25
- },
26
- {
27
- name: 'set_node_property',
28
- description: 'Set a single property on a node in a Godot scene file. For multiple properties on the same or different nodes, use batch_set_node_properties — one Godot process instead of N. Saves automatically. Primitives pass through; Vector2 ({x,y}), Vector3 ({x,y,z}), and Color ({r,g,b,a}) auto-convert. For other complex GDScript types (Resource, NodePath, etc.), use run_script. Errors if nodePath or property does not exist. Returns { success, nodePath, property }.',
29
- annotations: { idempotentHint: true },
30
- inputSchema: {
27
+ outputSchema: {
31
28
  type: 'object',
32
29
  properties: {
33
- projectPath: { type: 'string', description: 'Path to the Godot project directory' },
34
- scenePath: { type: 'string', description: 'Scene file path relative to the project' },
35
- nodePath: { type: 'string', description: 'Node path from scene root (e.g. "root/Player")' },
36
- property: {
37
- type: 'string',
38
- description: 'GDScript property name in snake_case (e.g. "position", "modulate", "collision_layer"). Use get_node_properties to discover valid names.',
30
+ results: {
31
+ type: 'array',
32
+ items: {
33
+ type: 'object',
34
+ properties: {
35
+ nodePath: { type: 'string' },
36
+ success: { type: 'boolean' },
37
+ error: { type: 'string' },
38
+ },
39
+ },
39
40
  },
40
- value: { description: 'New property value' },
41
41
  },
42
- required: ['projectPath', 'scenePath', 'nodePath', 'property', 'value'],
43
42
  },
44
43
  },
45
44
  {
46
- name: 'batch_set_node_properties',
47
- description: 'Set multiple node properties in a single Godot process preferred over chained set_node_property calls (avoids ~3s startup per call). Same value-conversion rules as set_node_property. abortOnError stops on first failure (default false continues). Saves once at the end. Returns { results: [{ nodePath, property, success?, error? }] }.',
45
+ name: 'set_node_properties',
46
+ description: 'Set one or more node properties on a scene in a single Godot process. Always-array: pass a single-element updates array for one-off edits. Vector2 ({x,y}), Vector3 ({x,y,z}), and Color ({r,g,b,a}) auto-convert; primitives pass through. For other complex GDScript types (Resource, NodePath, etc.), use run_script. abortOnError stops on first failure (default false continues). Saves once at the end. Returns: results[] with one entry per update in input order (success or error).',
48
47
  annotations: { idempotentHint: true },
49
48
  inputSchema: {
50
49
  type: 'object',
@@ -57,8 +56,14 @@ export const nodeToolDefinitions = [
57
56
  items: {
58
57
  type: 'object',
59
58
  properties: {
60
- nodePath: { type: 'string', description: 'Node path from scene root' },
61
- property: { type: 'string', description: 'GDScript property name in snake_case' },
59
+ nodePath: {
60
+ type: 'string',
61
+ description: 'Node path from scene root (e.g. "root/Player")',
62
+ },
63
+ property: {
64
+ type: 'string',
65
+ description: 'GDScript property name in snake_case (e.g. "position", "modulate", "collision_layer")',
66
+ },
62
67
  value: { description: 'New property value' },
63
68
  },
64
69
  required: ['nodePath', 'property', 'value'],
@@ -71,28 +76,27 @@ export const nodeToolDefinitions = [
71
76
  },
72
77
  required: ['projectPath', 'scenePath', 'updates'],
73
78
  },
74
- },
75
- {
76
- name: 'get_node_properties',
77
- description: "Read a node's current property values from a scene file. For multiple nodes, use batch_get_node_properties (one process). changedOnly:true filters out properties matching their class defaults — useful for compact diffs. Errors if nodePath does not exist. Returns { nodePath, nodeType, properties: { [key]: value } }.",
78
- annotations: { readOnlyHint: true },
79
- inputSchema: {
79
+ outputSchema: {
80
80
  type: 'object',
81
81
  properties: {
82
- projectPath: { type: 'string', description: 'Path to the Godot project directory' },
83
- scenePath: { type: 'string', description: 'Scene file path relative to the project' },
84
- nodePath: { type: 'string', description: 'Node path from scene root (e.g. "root/Player")' },
85
- changedOnly: {
86
- type: 'boolean',
87
- description: 'Only return properties whose values differ from their class defaults (default: false)',
82
+ results: {
83
+ type: 'array',
84
+ items: {
85
+ type: 'object',
86
+ properties: {
87
+ nodePath: { type: 'string' },
88
+ property: { type: 'string' },
89
+ success: { type: 'boolean' },
90
+ error: { type: 'string' },
91
+ },
92
+ },
88
93
  },
89
94
  },
90
- required: ['projectPath', 'scenePath', 'nodePath'],
91
95
  },
92
96
  },
93
97
  {
94
- name: 'batch_get_node_properties',
95
- description: 'Get properties from multiple nodes in a single Godot process preferred over chained get_node_properties calls. Per-node changedOnly toggles default-filtering individually. Returns { results: [{ nodePath, nodeType, properties?, error? }] }; failed reads include error and omit properties.',
98
+ name: 'get_node_properties',
99
+ description: "Read one or more nodes' current property values from a scene file in a single Godot process. Always-array: pass a single-element nodes array for one-off reads. Per-node changedOnly:true filters out properties matching class defaults (useful for compact diffs). Returns: { results: [{ nodePath, nodeType, properties?, error? }] }; failed reads include error and omit properties.",
96
100
  annotations: { readOnlyHint: true },
97
101
  inputSchema: {
98
102
  type: 'object',
@@ -105,7 +109,10 @@ export const nodeToolDefinitions = [
105
109
  items: {
106
110
  type: 'object',
107
111
  properties: {
108
- nodePath: { type: 'string', description: 'Node path from scene root' },
112
+ nodePath: {
113
+ type: 'string',
114
+ description: 'Node path from scene root (e.g. "root/Player")',
115
+ },
109
116
  changedOnly: {
110
117
  type: 'boolean',
111
118
  description: 'Only return properties differing from defaults (default: false)',
@@ -120,7 +127,7 @@ export const nodeToolDefinitions = [
120
127
  },
121
128
  {
122
129
  name: 'attach_script',
123
- description: 'Attach an existing GDScript file to a node in a scene. Use after writing the script with the standard file tools and validating it via the validate tool. Replaces any previously attached script. Saves automatically. Errors if scriptPath does not exist or nodePath is not found. Returns { success, nodePath, scriptPath }.',
130
+ description: 'Attach an existing GDScript file to a node in a scene. Use after writing the script with the standard file tools and validating it via the validate tool. Replaces any previously attached script. Saves automatically. Returns: success with the resolved nodePath and scriptPath that were attached. Errors if scriptPath does not exist or nodePath is not found.',
124
131
  annotations: { idempotentHint: true },
125
132
  inputSchema: {
126
133
  type: 'object',
@@ -135,10 +142,18 @@ export const nodeToolDefinitions = [
135
142
  },
136
143
  required: ['projectPath', 'scenePath', 'nodePath', 'scriptPath'],
137
144
  },
145
+ outputSchema: {
146
+ type: 'object',
147
+ properties: {
148
+ success: { type: 'boolean' },
149
+ nodePath: { type: 'string' },
150
+ scriptPath: { type: 'string' },
151
+ },
152
+ },
138
153
  },
139
154
  {
140
155
  name: 'get_scene_tree',
141
- description: 'Get the scene hierarchy as a nested tree of { name, type, path, children }. Use maxDepth:1 for a shallow listing of direct children only; default -1 returns the full tree. parentPath scopes the result to a subtree. Errors if scene does not exist or parentPath is not found.',
156
+ description: 'Get the scene hierarchy as a nested tree of { name, type, path, script, children }. Use maxDepth:1 for a shallow listing of direct children only; default -1 returns the full tree. parentPath scopes the result to a subtree. Returns the nested tree as JSON text. Errors if scene does not exist or parentPath is not found.',
142
157
  annotations: { readOnlyHint: true },
143
158
  inputSchema: {
144
159
  type: 'object',
@@ -159,7 +174,7 @@ export const nodeToolDefinitions = [
159
174
  },
160
175
  {
161
176
  name: 'duplicate_node',
162
- description: 'Duplicate a node and its descendants in a Godot scene. Use to clone a configured subtree without re-creating it node-by-node via add_node. newName defaults to the original name + "2"; targetParentPath defaults to the original parent. Saves automatically. Errors if nodePath does not exist or targetParentPath cannot accept children. Returns { success, originalPath, newPath }.',
177
+ description: 'Duplicate a node and its descendants in a Godot scene. Use to clone a configured subtree without re-creating it node-by-node via add_node. newName defaults to the original name + "2"; targetParentPath defaults to the original parent. Saves automatically. Returns: success with originalPath and the newPath where the duplicate now lives — use newPath for follow-up edits. Errors if nodePath does not exist or targetParentPath cannot accept children.',
163
178
  inputSchema: {
164
179
  type: 'object',
165
180
  properties: {
@@ -177,10 +192,18 @@ export const nodeToolDefinitions = [
177
192
  },
178
193
  required: ['projectPath', 'scenePath', 'nodePath'],
179
194
  },
195
+ outputSchema: {
196
+ type: 'object',
197
+ properties: {
198
+ success: { type: 'boolean' },
199
+ originalPath: { type: 'string' },
200
+ newPath: { type: 'string' },
201
+ },
202
+ },
180
203
  },
181
204
  {
182
205
  name: 'get_node_signals',
183
- description: 'List all signals defined on a node and their current connections. Use before connect_signal/disconnect_signal to verify signal/method names. Returns { nodePath, nodeType, signals: [{ name, connections: [{ signal, target, method }] }] }. The target field uses Godot absolute path format (/root/Scene/Node) — convert to scene-root-relative (root/Node) before passing to connect/disconnect_signal. Errors if node not found.',
206
+ description: 'List all signals defined on a node and their current connections. Use before connect_signal/disconnect_signal to verify signal/method names. The connections[].target field uses Godot absolute path format (/root/Scene/Node) — convert to scene-root-relative (root/Node) before passing to connect/disconnect_signal. Returns: nodeType and signals[], each with name and current connections (signal/target/method). Errors if node not found.',
184
207
  annotations: { readOnlyHint: true },
185
208
  inputSchema: {
186
209
  type: 'object',
@@ -191,10 +214,37 @@ export const nodeToolDefinitions = [
191
214
  },
192
215
  required: ['projectPath', 'scenePath', 'nodePath'],
193
216
  },
217
+ outputSchema: {
218
+ type: 'object',
219
+ properties: {
220
+ nodePath: { type: 'string' },
221
+ nodeType: { type: 'string' },
222
+ signals: {
223
+ type: 'array',
224
+ items: {
225
+ type: 'object',
226
+ properties: {
227
+ name: { type: 'string' },
228
+ connections: {
229
+ type: 'array',
230
+ items: {
231
+ type: 'object',
232
+ properties: {
233
+ signal: { type: 'string' },
234
+ target: { type: 'string' },
235
+ method: { type: 'string' },
236
+ },
237
+ },
238
+ },
239
+ },
240
+ },
241
+ },
242
+ },
243
+ },
194
244
  },
195
245
  {
196
246
  name: 'connect_signal',
197
- description: 'Connect a signal from one node to a method on another node. Saves automatically. Errors if the signal does not exist on the source node or the method does not exist on the target node.',
247
+ description: 'Connect a signal on a source node to a method on a target node, persisting the connection in the .tscn. Use after get_node_signals to confirm the signal name on the source and the method name on the target. Connecting the same signal+method pair twice creates a duplicate connection — call get_node_signals first if uncertain. Saves automatically. Returns a plain-text confirmation naming the source, signal, target, and method. Errors if the signal does not exist on the source node or the method does not exist on the target node.',
198
248
  inputSchema: {
199
249
  type: 'object',
200
250
  properties: {
@@ -219,7 +269,8 @@ export const nodeToolDefinitions = [
219
269
  },
220
270
  {
221
271
  name: 'disconnect_signal',
222
- description: 'Disconnect a signal connection between two nodes. Saves automatically. Errors if the connection does not exist — use get_node_signals first to verify.',
272
+ description: 'Remove an existing signal connection between two nodes, persisting the change in the .tscn. Use get_node_signals first to confirm the connection exists; recovery requires reconnecting via connect_signal. Saves automatically. Returns a plain-text confirmation naming the disconnected signal and target. Errors if the connection does not exist.',
273
+ annotations: { destructiveHint: true },
223
274
  inputSchema: {
224
275
  type: 'object',
225
276
  properties: {
@@ -235,71 +286,29 @@ export const nodeToolDefinitions = [
235
286
  },
236
287
  ];
237
288
  // --- Handlers ---
238
- export async function handleDeleteNode(runner, args) {
289
+ export async function handleDeleteNodes(runner, args) {
239
290
  args = normalizeParameters(args);
240
291
  const v = validateSceneArgs(args);
241
292
  if ('isError' in v)
242
293
  return v;
243
- if (!args.nodePath || !validatePath(args.nodePath)) {
244
- return createErrorResponse('Valid nodePath is required', [
245
- 'Provide the node path (e.g. "root/Player")',
294
+ if (!args.nodePaths || !Array.isArray(args.nodePaths) || args.nodePaths.length === 0) {
295
+ return createErrorResponse('nodePaths array is required', [
296
+ 'Provide a non-empty array of node paths (e.g. ["root/Player"])',
246
297
  ]);
247
298
  }
248
- try {
249
- const params = { scenePath: args.scenePath, nodePath: args.nodePath };
250
- const { stdout, stderr } = await runner.executeOperation('delete_node', params, v.projectPath);
251
- if (!stdout.trim()) {
252
- return createErrorResponse(`Failed to delete node: ${extractGdError(stderr)}`, [
253
- 'Check if the node path is correct',
299
+ for (const p of args.nodePaths) {
300
+ if (typeof p !== 'string' || !validateNodePath(p)) {
301
+ return createErrorResponse('Invalid nodePath in nodePaths', [
302
+ 'Provide a scene-tree path without ".." (e.g. "root/Player")',
254
303
  ]);
255
304
  }
256
- return { content: [{ type: 'text', text: stdout }] };
257
- }
258
- catch (error) {
259
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
260
- return createErrorResponse(`Failed to delete node: ${errorMessage}`, [
261
- 'Ensure Godot is installed correctly',
262
- ]);
263
305
  }
306
+ const params = { scenePath: args.scenePath, nodePaths: args.nodePaths };
307
+ return executeSceneOp(runner, 'delete_nodes', params, v.projectPath, 'Failed to delete nodes', [
308
+ 'Check if the node paths are correct',
309
+ ]);
264
310
  }
265
- export async function handleSetNodeProperty(runner, args) {
266
- args = normalizeParameters(args);
267
- const v = validateSceneArgs(args);
268
- if ('isError' in v)
269
- return v;
270
- if (!args.nodePath || !validatePath(args.nodePath)) {
271
- return createErrorResponse('Valid nodePath is required', [
272
- 'Provide the node path (e.g. "root/Player")',
273
- ]);
274
- }
275
- if (!args.property || args.value === undefined) {
276
- return createErrorResponse('property and value are required', [
277
- 'Provide both property name and value',
278
- ]);
279
- }
280
- try {
281
- const params = {
282
- scenePath: args.scenePath,
283
- nodePath: args.nodePath,
284
- property: args.property,
285
- value: args.value,
286
- };
287
- const { stdout, stderr } = await runner.executeOperation('set_node_property', params, v.projectPath);
288
- if (!stdout.trim()) {
289
- return createErrorResponse(`Failed to update property: ${extractGdError(stderr)}`, [
290
- 'Check if the property name is valid for this node type',
291
- ]);
292
- }
293
- return { content: [{ type: 'text', text: stdout }] };
294
- }
295
- catch (error) {
296
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
297
- return createErrorResponse(`Failed to set node property: ${errorMessage}`, [
298
- 'Ensure Godot is installed correctly',
299
- ]);
300
- }
301
- }
302
- export async function handleBatchSetNodeProperties(runner, args) {
311
+ export async function handleSetNodeProperties(runner, args) {
303
312
  args = normalizeParameters(args);
304
313
  const v = validateSceneArgs(args);
305
314
  if ('isError' in v)
@@ -309,58 +318,14 @@ export async function handleBatchSetNodeProperties(runner, args) {
309
318
  'Provide an array of { nodePath, property, value }',
310
319
  ]);
311
320
  }
312
- try {
313
- const snakeUpdates = args.updates.map((u) => convertCamelToSnakeCase(u));
314
- const params = {
315
- scenePath: args.scenePath,
316
- updates: snakeUpdates,
317
- abortOnError: args.abortOnError ?? false,
318
- };
319
- const { stdout, stderr } = await runner.executeOperation('batch_set_node_properties', params, v.projectPath);
320
- if (!stdout.trim()) {
321
- return createErrorResponse(`Batch update failed: ${extractGdError(stderr)}`, [
322
- 'Check node paths and property names',
323
- ]);
324
- }
325
- return { content: [{ type: 'text', text: stdout }] };
326
- }
327
- catch (error) {
328
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
329
- return createErrorResponse(`Batch set properties failed: ${errorMessage}`, [
330
- 'Ensure Godot is installed correctly',
331
- ]);
332
- }
321
+ const params = {
322
+ scenePath: args.scenePath,
323
+ updates: args.updates,
324
+ abortOnError: args.abortOnError ?? false,
325
+ };
326
+ return executeSceneOp(runner, 'set_node_properties', params, v.projectPath, 'Failed to set node properties', ['Check node paths and property names']);
333
327
  }
334
328
  export async function handleGetNodeProperties(runner, args) {
335
- args = normalizeParameters(args);
336
- const v = validateSceneArgs(args);
337
- if ('isError' in v)
338
- return v;
339
- if (!args.nodePath || !validatePath(args.nodePath)) {
340
- return createErrorResponse('Valid nodePath is required', [
341
- 'Provide the node path (e.g. "root/Player")',
342
- ]);
343
- }
344
- try {
345
- const params = { scenePath: args.scenePath, nodePath: args.nodePath };
346
- if (args.changedOnly)
347
- params.changedOnly = args.changedOnly;
348
- const { stdout, stderr } = await runner.executeOperation('get_node_properties', params, v.projectPath);
349
- if (!stdout.trim()) {
350
- return createErrorResponse(`Failed to get properties: ${extractGdError(stderr)}`, [
351
- 'Check if the node path is correct',
352
- ]);
353
- }
354
- return { content: [{ type: 'text', text: stdout }] };
355
- }
356
- catch (error) {
357
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
358
- return createErrorResponse(`Failed to get node properties: ${errorMessage}`, [
359
- 'Ensure Godot is installed correctly',
360
- ]);
361
- }
362
- }
363
- export async function handleBatchGetNodeProperties(runner, args) {
364
329
  args = normalizeParameters(args);
365
330
  const v = validateSceneArgs(args);
366
331
  if ('isError' in v)
@@ -370,37 +335,22 @@ export async function handleBatchGetNodeProperties(runner, args) {
370
335
  'Provide an array of { nodePath, changedOnly? }',
371
336
  ]);
372
337
  }
373
- try {
374
- const snakeNodes = args.nodes.map((n) => convertCamelToSnakeCase(n));
375
- const params = { scenePath: args.scenePath, nodes: snakeNodes };
376
- const { stdout, stderr } = await runner.executeOperation('batch_get_node_properties', params, v.projectPath);
377
- if (!stdout.trim()) {
378
- return createErrorResponse(`Batch get_properties failed: ${extractGdError(stderr)}`, [
379
- 'Check node paths',
380
- ]);
381
- }
382
- return { content: [{ type: 'text', text: stdout }] };
383
- }
384
- catch (error) {
385
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
386
- return createErrorResponse(`Batch get properties failed: ${errorMessage}`, [
387
- 'Ensure Godot is installed correctly',
388
- ]);
389
- }
338
+ const params = { scenePath: args.scenePath, nodes: args.nodes };
339
+ return executeSceneOp(runner, 'get_node_properties', params, v.projectPath, 'Failed to get node properties', ['Check node paths']);
390
340
  }
391
341
  export async function handleAttachScript(runner, args) {
392
342
  args = normalizeParameters(args);
393
343
  const v = validateSceneArgs(args);
394
344
  if ('isError' in v)
395
345
  return v;
396
- if (!args.nodePath || !validatePath(args.nodePath)) {
346
+ if (!args.nodePath || !validateNodePath(args.nodePath)) {
397
347
  return createErrorResponse('Valid nodePath is required', [
398
348
  'Provide the node path (e.g. "root/Player")',
399
349
  ]);
400
350
  }
401
- if (!args.scriptPath || !validatePath(args.scriptPath)) {
351
+ if (!args.scriptPath || !validateSubPath(v.projectPath, args.scriptPath)) {
402
352
  return createErrorResponse('Valid scriptPath is required', [
403
- 'Provide the script path relative to the project',
353
+ 'Provide a relative script path that stays inside the project directory',
404
354
  ]);
405
355
  }
406
356
  const scriptFullPath = join(v.projectPath, args.scriptPath);
@@ -409,123 +359,72 @@ export async function handleAttachScript(runner, args) {
409
359
  'Create the script file first',
410
360
  ]);
411
361
  }
412
- try {
413
- const params = {
414
- scenePath: args.scenePath,
415
- nodePath: args.nodePath,
416
- scriptPath: args.scriptPath,
417
- };
418
- const { stdout, stderr } = await runner.executeOperation('attach_script', params, v.projectPath);
419
- if (!stdout.trim()) {
420
- return createErrorResponse(`Failed to attach script: ${extractGdError(stderr)}`, [
421
- 'Ensure the script is valid for this node type',
422
- ]);
423
- }
424
- return { content: [{ type: 'text', text: stdout }] };
425
- }
426
- catch (error) {
427
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
428
- return createErrorResponse(`Failed to attach script: ${errorMessage}`, [
429
- 'Ensure Godot is installed correctly',
430
- ]);
431
- }
362
+ const params = {
363
+ scenePath: args.scenePath,
364
+ nodePath: args.nodePath,
365
+ scriptPath: args.scriptPath,
366
+ };
367
+ return executeSceneOp(runner, 'attach_script', params, v.projectPath, 'Failed to attach script', [
368
+ 'Ensure the script is valid for this node type',
369
+ ]);
432
370
  }
433
371
  export async function handleGetSceneTree(runner, args) {
434
372
  args = normalizeParameters(args);
435
373
  const v = validateSceneArgs(args);
436
374
  if ('isError' in v)
437
375
  return v;
438
- if (args.parentPath && !validatePath(args.parentPath)) {
439
- return createErrorResponse('Invalid parentPath', ['Provide a valid path without ".."']);
440
- }
441
- try {
442
- const params = { scenePath: args.scenePath };
443
- if (args.parentPath)
444
- params.parentPath = args.parentPath;
445
- if (typeof args.maxDepth === 'number')
446
- params.maxDepth = args.maxDepth;
447
- const { stdout, stderr } = await runner.executeOperation('get_scene_tree', params, v.projectPath);
448
- if (!stdout.trim()) {
449
- return createErrorResponse(`Failed to get scene tree: ${extractGdError(stderr)}`, [
450
- 'Ensure the scene is valid',
451
- ]);
452
- }
453
- return { content: [{ type: 'text', text: stdout }] };
454
- }
455
- catch (error) {
456
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
457
- return createErrorResponse(`Failed to get scene tree: ${errorMessage}`, [
458
- 'Ensure Godot is installed correctly',
376
+ if (args.parentPath && !validateNodePath(args.parentPath)) {
377
+ return createErrorResponse('Invalid parentPath', [
378
+ 'Provide a scene-tree path without ".." (e.g. "root/Player")',
459
379
  ]);
460
380
  }
381
+ const params = { scenePath: args.scenePath };
382
+ if (args.parentPath)
383
+ params.parentPath = args.parentPath;
384
+ if (typeof args.maxDepth === 'number')
385
+ params.maxDepth = args.maxDepth;
386
+ return executeSceneOp(runner, 'get_scene_tree', params, v.projectPath, 'Failed to get scene tree', ['Ensure the scene is valid']);
461
387
  }
462
388
  export async function handleDuplicateNode(runner, args) {
463
389
  args = normalizeParameters(args);
464
390
  const v = validateSceneArgs(args);
465
391
  if ('isError' in v)
466
392
  return v;
467
- if (!args.nodePath || !validatePath(args.nodePath)) {
393
+ if (!args.nodePath || !validateNodePath(args.nodePath)) {
468
394
  return createErrorResponse('Valid nodePath is required', [
469
395
  'Provide the node path to duplicate',
470
396
  ]);
471
397
  }
472
- if (args.targetParentPath && !validatePath(args.targetParentPath)) {
473
- return createErrorResponse('Invalid targetParentPath', ['Provide a valid path without ".."']);
474
- }
475
- try {
476
- const params = { scenePath: args.scenePath, nodePath: args.nodePath };
477
- if (args.newName)
478
- params.newName = args.newName;
479
- if (args.targetParentPath)
480
- params.targetParentPath = args.targetParentPath;
481
- const { stdout, stderr } = await runner.executeOperation('duplicate_node', params, v.projectPath);
482
- if (!stdout.trim()) {
483
- return createErrorResponse(`Failed to duplicate node: ${extractGdError(stderr)}`, [
484
- 'Check if the node path and target parent path are correct',
485
- ]);
486
- }
487
- return { content: [{ type: 'text', text: stdout }] };
488
- }
489
- catch (error) {
490
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
491
- return createErrorResponse(`Failed to duplicate node: ${errorMessage}`, [
492
- 'Ensure Godot is installed correctly',
398
+ if (args.targetParentPath && !validateNodePath(args.targetParentPath)) {
399
+ return createErrorResponse('Invalid targetParentPath', [
400
+ 'Provide a scene-tree path without ".." (e.g. "root/Player")',
493
401
  ]);
494
402
  }
403
+ const params = { scenePath: args.scenePath, nodePath: args.nodePath };
404
+ if (args.newName)
405
+ params.newName = args.newName;
406
+ if (args.targetParentPath)
407
+ params.targetParentPath = args.targetParentPath;
408
+ return executeSceneOp(runner, 'duplicate_node', params, v.projectPath, 'Failed to duplicate node', ['Check if the node path and target parent path are correct']);
495
409
  }
496
410
  export async function handleGetNodeSignals(runner, args) {
497
411
  args = normalizeParameters(args);
498
412
  const v = validateSceneArgs(args);
499
413
  if ('isError' in v)
500
414
  return v;
501
- if (!args.nodePath || !validatePath(args.nodePath)) {
415
+ if (!args.nodePath || !validateNodePath(args.nodePath)) {
502
416
  return createErrorResponse('Valid nodePath is required', [
503
417
  'Provide the node path (e.g. "root/Button")',
504
418
  ]);
505
419
  }
506
- try {
507
- const params = { scenePath: args.scenePath, nodePath: args.nodePath };
508
- const { stdout, stderr } = await runner.executeOperation('get_node_signals', params, v.projectPath);
509
- if (!stdout.trim()) {
510
- return createErrorResponse(`Failed to get signals: ${extractGdError(stderr)}`, [
511
- 'Check if the node path is correct',
512
- ]);
513
- }
514
- return { content: [{ type: 'text', text: stdout }] };
515
- }
516
- catch (error) {
517
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
518
- return createErrorResponse(`Failed to get node signals: ${errorMessage}`, [
519
- 'Ensure Godot is installed correctly',
520
- ]);
521
- }
420
+ const params = { scenePath: args.scenePath, nodePath: args.nodePath };
421
+ return executeSceneOp(runner, 'get_node_signals', params, v.projectPath, 'Failed to get node signals', ['Check if the node path is correct']);
522
422
  }
523
- export async function handleConnectSignal(runner, args) {
524
- args = normalizeParameters(args);
423
+ function validateSignalArgs(args) {
525
424
  const v = validateSceneArgs(args);
526
425
  if ('isError' in v)
527
426
  return v;
528
- if (!args.nodePath || !validatePath(args.nodePath)) {
427
+ if (!args.nodePath || !validateNodePath(args.nodePath)) {
529
428
  return createErrorResponse('Valid nodePath is required', ['Provide the source node path']);
530
429
  }
531
430
  if (!args.signal || !args.targetNodePath || !args.method) {
@@ -533,69 +432,46 @@ export async function handleConnectSignal(runner, args) {
533
432
  'Provide all three parameters',
534
433
  ]);
535
434
  }
536
- if (!validatePath(args.targetNodePath)) {
537
- return createErrorResponse('Invalid targetNodePath', ['Provide a valid path without ".."']);
538
- }
539
- try {
540
- const params = {
541
- scenePath: args.scenePath,
542
- nodePath: args.nodePath,
543
- signal: args.signal,
544
- targetNodePath: args.targetNodePath,
545
- method: args.method,
546
- };
547
- const { stdout, stderr } = await runner.executeOperation('connect_signal', params, v.projectPath);
548
- if (!stdout.trim()) {
549
- return createErrorResponse(`Failed to connect signal: ${extractGdError(stderr)}`, [
550
- 'Ensure the signal exists on the source node and the method exists on the target node',
551
- ]);
552
- }
553
- return { content: [{ type: 'text', text: stdout }] };
554
- }
555
- catch (error) {
556
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
557
- return createErrorResponse(`Failed to connect signal: ${errorMessage}`, [
558
- 'Ensure Godot is installed correctly',
435
+ if (!validateNodePath(args.targetNodePath)) {
436
+ return createErrorResponse('Invalid targetNodePath', [
437
+ 'Provide a scene-tree path without ".." (e.g. "root/Player")',
559
438
  ]);
560
439
  }
440
+ return {
441
+ projectPath: v.projectPath,
442
+ scenePath: v.scenePath,
443
+ nodePath: args.nodePath,
444
+ signal: args.signal,
445
+ targetNodePath: args.targetNodePath,
446
+ method: args.method,
447
+ };
448
+ }
449
+ export async function handleConnectSignal(runner, args) {
450
+ args = normalizeParameters(args);
451
+ const v = validateSignalArgs(args);
452
+ if ('isError' in v)
453
+ return v;
454
+ const params = {
455
+ scenePath: v.scenePath,
456
+ nodePath: v.nodePath,
457
+ signal: v.signal,
458
+ targetNodePath: v.targetNodePath,
459
+ method: v.method,
460
+ };
461
+ return executeSceneOp(runner, 'connect_signal', params, v.projectPath, 'Failed to connect signal', ['Ensure the signal exists on the source node and the method exists on the target node']);
561
462
  }
562
463
  export async function handleDisconnectSignal(runner, args) {
563
464
  args = normalizeParameters(args);
564
- const v = validateSceneArgs(args);
465
+ const v = validateSignalArgs(args);
565
466
  if ('isError' in v)
566
467
  return v;
567
- if (!args.nodePath || !validatePath(args.nodePath)) {
568
- return createErrorResponse('Valid nodePath is required', ['Provide the source node path']);
569
- }
570
- if (!args.signal || !args.targetNodePath || !args.method) {
571
- return createErrorResponse('signal, targetNodePath, and method are required', [
572
- 'Provide all three parameters',
573
- ]);
574
- }
575
- if (!validatePath(args.targetNodePath)) {
576
- return createErrorResponse('Invalid targetNodePath', ['Provide a valid path without ".."']);
577
- }
578
- try {
579
- const params = {
580
- scenePath: args.scenePath,
581
- nodePath: args.nodePath,
582
- signal: args.signal,
583
- targetNodePath: args.targetNodePath,
584
- method: args.method,
585
- };
586
- const { stdout, stderr } = await runner.executeOperation('disconnect_signal', params, v.projectPath);
587
- if (!stdout.trim()) {
588
- return createErrorResponse(`Failed to disconnect signal: ${extractGdError(stderr)}`, [
589
- 'Ensure the signal connection exists before trying to disconnect it',
590
- ]);
591
- }
592
- return { content: [{ type: 'text', text: stdout }] };
593
- }
594
- catch (error) {
595
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
596
- return createErrorResponse(`Failed to disconnect signal: ${errorMessage}`, [
597
- 'Ensure Godot is installed correctly',
598
- ]);
599
- }
468
+ const params = {
469
+ scenePath: v.scenePath,
470
+ nodePath: v.nodePath,
471
+ signal: v.signal,
472
+ targetNodePath: v.targetNodePath,
473
+ method: v.method,
474
+ };
475
+ return executeSceneOp(runner, 'disconnect_signal', params, v.projectPath, 'Failed to disconnect signal', ['Ensure the signal connection exists before trying to disconnect it']);
600
476
  }
601
477
  //# sourceMappingURL=node-tools.js.map