godot-mcp-runtime 2.2.0 → 2.2.2
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 +61 -64
- package/dist/dispatch.d.ts +26 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/dispatch.js +70 -0
- package/dist/dispatch.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -118
- package/dist/index.js.map +1 -1
- package/dist/scripts/godot_operations.gd +1117 -1117
- package/dist/scripts/mcp_bridge.gd +9 -1
- package/dist/tools/node-tools.d.ts +1 -1
- package/dist/tools/node-tools.d.ts.map +1 -1
- package/dist/tools/node-tools.js +168 -56
- package/dist/tools/node-tools.js.map +1 -1
- package/dist/tools/project-tools.d.ts +4 -11
- package/dist/tools/project-tools.d.ts.map +1 -1
- package/dist/tools/project-tools.js +489 -153
- package/dist/tools/project-tools.js.map +1 -1
- package/dist/tools/scene-tools.d.ts +1 -1
- package/dist/tools/scene-tools.d.ts.map +1 -1
- package/dist/tools/scene-tools.js +168 -44
- package/dist/tools/scene-tools.js.map +1 -1
- package/dist/tools/validate-tools.d.ts +1 -1
- package/dist/tools/validate-tools.d.ts.map +1 -1
- package/dist/tools/validate-tools.js +44 -15
- package/dist/tools/validate-tools.js.map +1 -1
- package/dist/utils/godot-runner.d.ts +39 -1
- package/dist/utils/godot-runner.d.ts.map +1 -1
- package/dist/utils/godot-runner.js +200 -36
- package/dist/utils/godot-runner.js.map +1 -1
- package/package.json +22 -4
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { join, basename, sep } from 'path';
|
|
2
2
|
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
3
3
|
import { normalizeParameters, validatePath, validateProjectArgs, createErrorResponse, logDebug, } from '../utils/godot-runner.js';
|
|
4
|
+
const MAX_RUNTIME_ERROR_CONTEXT_LINES = 30;
|
|
4
5
|
function parseAutoloads(projectFilePath) {
|
|
5
6
|
const content = readFileSync(projectFilePath, 'utf8');
|
|
6
7
|
const autoloads = [];
|
|
@@ -27,7 +28,7 @@ function addAutoloadEntry(projectFilePath, name, path, singleton) {
|
|
|
27
28
|
const content = readFileSync(projectFilePath, 'utf8');
|
|
28
29
|
const lines = content.split('\n');
|
|
29
30
|
const entry = `${name}="${singleton ? '*' : ''}${normalizeAutoloadPath(path)}"`;
|
|
30
|
-
const sectionIdx = lines.findIndex(l => l.trim() === '[autoload]');
|
|
31
|
+
const sectionIdx = lines.findIndex((l) => l.trim() === '[autoload]');
|
|
31
32
|
if (sectionIdx === -1) {
|
|
32
33
|
writeFileSync(projectFilePath, content.trimEnd() + '\n\n[autoload]\n' + entry + '\n', 'utf8');
|
|
33
34
|
return;
|
|
@@ -44,7 +45,7 @@ function removeAutoloadEntry(projectFilePath, name) {
|
|
|
44
45
|
const lines = content.split('\n');
|
|
45
46
|
let inAutoloadSection = false;
|
|
46
47
|
let removed = false;
|
|
47
|
-
const newLines = lines.filter(line => {
|
|
48
|
+
const newLines = lines.filter((line) => {
|
|
48
49
|
const trimmed = line.trim();
|
|
49
50
|
if (trimmed.startsWith('[')) {
|
|
50
51
|
inAutoloadSection = trimmed === '[autoload]';
|
|
@@ -68,7 +69,7 @@ function updateAutoloadEntry(projectFilePath, name, newPath, singleton) {
|
|
|
68
69
|
const lines = content.split('\n');
|
|
69
70
|
let inAutoloadSection = false;
|
|
70
71
|
let updated = false;
|
|
71
|
-
const newLines = lines.map(line => {
|
|
72
|
+
const newLines = lines.map((line) => {
|
|
72
73
|
const trimmed = line.trim();
|
|
73
74
|
if (trimmed.startsWith('[')) {
|
|
74
75
|
inAutoloadSection = trimmed === '[autoload]';
|
|
@@ -107,7 +108,7 @@ export const projectToolDefinitions = [
|
|
|
107
108
|
},
|
|
108
109
|
{
|
|
109
110
|
name: 'run_project',
|
|
110
|
-
description: 'Run a Godot project
|
|
111
|
+
description: 'Run a Godot project with stdout/stderr captured. Preferred path for runtime tools. Required before calling take_screenshot, simulate_input, get_ui_elements, run_script, or get_debug_output unless you intentionally use attach_project for a manually launched game. Verifies MCP bridge readiness before returning success. Call stop_project when done.',
|
|
111
112
|
inputSchema: {
|
|
112
113
|
type: 'object',
|
|
113
114
|
properties: {
|
|
@@ -129,7 +130,7 @@ export const projectToolDefinitions = [
|
|
|
129
130
|
},
|
|
130
131
|
{
|
|
131
132
|
name: 'attach_project',
|
|
132
|
-
description: 'Attach runtime MCP tools to a project without spawning Godot. This injects the McpBridge autoload and marks the project as the active runtime session so you can launch the game manually from your own shell, then use take_screenshot, simulate_input, get_ui_elements, and run_script against that running game. Use detach_project or stop_project when done. get_debug_output is not available in attached mode because stdout/stderr are not captured.',
|
|
133
|
+
description: 'Attach runtime MCP tools to a project without spawning Godot. This injects the McpBridge autoload and marks the project as the active runtime session so you can launch the game manually from your own shell, then use take_screenshot, simulate_input, get_ui_elements, and run_script against that running game. Set waitForBridge to true after launching Godot to block until the bridge is confirmed ready. Use detach_project or stop_project when done. get_debug_output is not available in attached mode because stdout/stderr are not captured.',
|
|
133
134
|
inputSchema: {
|
|
134
135
|
type: 'object',
|
|
135
136
|
properties: {
|
|
@@ -137,6 +138,10 @@ export const projectToolDefinitions = [
|
|
|
137
138
|
type: 'string',
|
|
138
139
|
description: 'Path to the Godot project directory',
|
|
139
140
|
},
|
|
141
|
+
waitForBridge: {
|
|
142
|
+
type: 'boolean',
|
|
143
|
+
description: 'If true, poll the bridge until it responds (up to 15 seconds). Use this after Godot is already running to confirm runtime tools are ready. Defaults to false.',
|
|
144
|
+
},
|
|
140
145
|
},
|
|
141
146
|
required: ['projectPath'],
|
|
142
147
|
},
|
|
@@ -207,7 +212,7 @@ export const projectToolDefinitions = [
|
|
|
207
212
|
},
|
|
208
213
|
{
|
|
209
214
|
name: 'take_screenshot',
|
|
210
|
-
description: 'Capture a PNG screenshot of the running Godot viewport. Requires
|
|
215
|
+
description: 'Capture a PNG screenshot of the running Godot viewport. Requires an active runtime session (run_project or attach_project). Returns the image inline. Screenshots are also saved to .mcp/screenshots/ in the project directory.',
|
|
211
216
|
inputSchema: {
|
|
212
217
|
type: 'object',
|
|
213
218
|
properties: {
|
|
@@ -221,7 +226,7 @@ export const projectToolDefinitions = [
|
|
|
221
226
|
},
|
|
222
227
|
{
|
|
223
228
|
name: 'simulate_input',
|
|
224
|
-
description: 'Simulate batched sequential input in a running Godot project. Requires
|
|
229
|
+
description: 'Simulate batched sequential input in a running Godot project. Requires an active runtime session (run_project or attach_project). Use get_ui_elements first to discover element names and paths for click_element actions.\n\nEach action object requires a "type" field. Valid types and their specific fields:\n- key: keyboard event (key: string, pressed: bool, shift/ctrl/alt: bool)\n- mouse_button: click at coordinates (x, y: number, button: "left"|"right"|"middle", pressed: bool, double_click: bool)\n- mouse_motion: move cursor (x, y: number, relative_x, relative_y: number)\n- click_element: click a UI element by node path or node name (element: string, button, double_click)\n- action: fire a Godot input action (action: string, pressed: bool, strength: 0–1)\n- wait: pause between actions (ms: number)\n\nExamples:\n1. Press and release Space: [{type:"key",key:"Space",pressed:true},{type:"wait",ms:100},{type:"key",key:"Space",pressed:false}]\n2. Click a UI button (discover path with get_ui_elements first): [{type:"click_element",element:"StartButton"}]\n3. Left-click at viewport coordinates: [{type:"mouse_button",x:400,y:300,button:"left",pressed:true},{type:"mouse_button",x:400,y:300,button:"left",pressed:false}]\n4. Fire a Godot action: [{type:"action",action:"jump",pressed:true},{type:"wait",ms:200},{type:"action",action:"jump",pressed:false}]\n5. Type "hello": [{type:"key",key:"H",pressed:true},{type:"key",key:"H",pressed:false},{type:"key",key:"E",pressed:true},{type:"key",key:"E",pressed:false},{type:"key",key:"L",pressed:true},{type:"key",key:"L",pressed:false},{type:"key",key:"L",pressed:true},{type:"key",key:"L",pressed:false},{type:"key",key:"O",pressed:true},{type:"key",key:"O",pressed:false}]',
|
|
225
230
|
inputSchema: {
|
|
226
231
|
type: 'object',
|
|
227
232
|
properties: {
|
|
@@ -236,21 +241,58 @@ export const projectToolDefinitions = [
|
|
|
236
241
|
enum: ['key', 'mouse_button', 'mouse_motion', 'click_element', 'action', 'wait'],
|
|
237
242
|
description: 'The type of input action',
|
|
238
243
|
},
|
|
239
|
-
key: {
|
|
240
|
-
|
|
244
|
+
key: {
|
|
245
|
+
type: 'string',
|
|
246
|
+
description: '[key] Key name (e.g. "W", "Space", "Escape", "Up")',
|
|
247
|
+
},
|
|
248
|
+
pressed: {
|
|
249
|
+
type: 'boolean',
|
|
250
|
+
description: '[key, mouse_button, action] Whether the input is pressed (true) or released (false)',
|
|
251
|
+
},
|
|
241
252
|
shift: { type: 'boolean', description: '[key] Shift modifier' },
|
|
242
253
|
ctrl: { type: 'boolean', description: '[key] Ctrl modifier' },
|
|
243
254
|
alt: { type: 'boolean', description: '[key] Alt modifier' },
|
|
244
|
-
button: {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
255
|
+
button: {
|
|
256
|
+
type: 'string',
|
|
257
|
+
enum: ['left', 'right', 'middle'],
|
|
258
|
+
description: '[mouse_button, click_element] Mouse button (default: left)',
|
|
259
|
+
},
|
|
260
|
+
x: {
|
|
261
|
+
type: 'number',
|
|
262
|
+
description: '[mouse_button, mouse_motion] X position in viewport pixels',
|
|
263
|
+
},
|
|
264
|
+
y: {
|
|
265
|
+
type: 'number',
|
|
266
|
+
description: '[mouse_button, mouse_motion] Y position in viewport pixels',
|
|
267
|
+
},
|
|
268
|
+
relative_x: {
|
|
269
|
+
type: 'number',
|
|
270
|
+
description: '[mouse_motion] Relative X movement in pixels',
|
|
271
|
+
},
|
|
272
|
+
relative_y: {
|
|
273
|
+
type: 'number',
|
|
274
|
+
description: '[mouse_motion] Relative Y movement in pixels',
|
|
275
|
+
},
|
|
276
|
+
double_click: {
|
|
277
|
+
type: 'boolean',
|
|
278
|
+
description: '[mouse_button, click_element] Double click',
|
|
279
|
+
},
|
|
280
|
+
element: {
|
|
281
|
+
type: 'string',
|
|
282
|
+
description: '[click_element] Identifies the UI element to click. Accepts: absolute node path (e.g. "/root/HUD/Button"), relative node path, or node name (BFS matched). Use get_ui_elements to discover valid names and paths.',
|
|
283
|
+
},
|
|
284
|
+
action: {
|
|
285
|
+
type: 'string',
|
|
286
|
+
description: '[action] Godot input action name (as defined in Project Settings > Input Map)',
|
|
287
|
+
},
|
|
288
|
+
strength: {
|
|
289
|
+
type: 'number',
|
|
290
|
+
description: '[action] Action strength (0–1, default 1.0)',
|
|
291
|
+
},
|
|
292
|
+
ms: {
|
|
293
|
+
type: 'number',
|
|
294
|
+
description: '[wait] Duration in milliseconds to pause before the next action',
|
|
295
|
+
},
|
|
254
296
|
},
|
|
255
297
|
required: ['type'],
|
|
256
298
|
},
|
|
@@ -261,7 +303,7 @@ export const projectToolDefinitions = [
|
|
|
261
303
|
},
|
|
262
304
|
{
|
|
263
305
|
name: 'get_ui_elements',
|
|
264
|
-
description: 'Get Control nodes from a running Godot project with their positions, sizes, types, and text. Requires
|
|
306
|
+
description: 'Get Control nodes from a running Godot project with their positions, sizes, types, and text. Requires an active runtime session (run_project or attach_project). Call this before simulate_input with click_element to discover valid element names and paths. Returns: { elements: [{ name, path, type, rect: {x,y,width,height}, visible, text? (Button/Label/LineEdit/TextEdit), disabled? (buttons), tooltip? }] }',
|
|
265
307
|
inputSchema: {
|
|
266
308
|
type: 'object',
|
|
267
309
|
properties: {
|
|
@@ -279,7 +321,7 @@ export const projectToolDefinitions = [
|
|
|
279
321
|
},
|
|
280
322
|
{
|
|
281
323
|
name: 'run_script',
|
|
282
|
-
description: 'Execute a custom GDScript in the live running project with full scene tree access. Requires run_project first. Script must extend RefCounted and define func execute(scene_tree: SceneTree) -> Variant. Return values are JSON-serialized (primitives, Vector2/3, Color, Dictionary, Array, and Node path strings are supported). Use print() for debug output — it appears in get_debug_output, not in the script result.',
|
|
324
|
+
description: 'Execute a custom GDScript in the live running project with full scene tree access. Requires run_project first. Script must extend RefCounted and define func execute(scene_tree: SceneTree) -> Variant. Return values are JSON-serialized (primitives, Vector2/3, Color, Dictionary, Array, and Node path strings are supported). Use print() for debug output — it appears in get_debug_output, not in the script result. In spawned mode, runtime errors emitted to stderr are detected and either escalated (when the script returns null) or surfaced as warnings. In attached mode a null result includes a caveat since stderr is not captured.',
|
|
283
325
|
inputSchema: {
|
|
284
326
|
type: 'object',
|
|
285
327
|
properties: {
|
|
@@ -313,9 +355,18 @@ export const projectToolDefinitions = [
|
|
|
313
355
|
type: 'object',
|
|
314
356
|
properties: {
|
|
315
357
|
projectPath: { type: 'string', description: 'Path to the Godot project directory' },
|
|
316
|
-
autoloadName: {
|
|
317
|
-
|
|
318
|
-
|
|
358
|
+
autoloadName: {
|
|
359
|
+
type: 'string',
|
|
360
|
+
description: 'Name of the autoload node (e.g. "MyManager")',
|
|
361
|
+
},
|
|
362
|
+
autoloadPath: {
|
|
363
|
+
type: 'string',
|
|
364
|
+
description: 'Path to the script or scene (e.g. "res://autoload/my_manager.gd" or "autoload/my_manager.gd")',
|
|
365
|
+
},
|
|
366
|
+
singleton: {
|
|
367
|
+
type: 'boolean',
|
|
368
|
+
description: 'Register as a globally accessible singleton by name (default: true)',
|
|
369
|
+
},
|
|
319
370
|
},
|
|
320
371
|
required: ['projectPath', 'autoloadName', 'autoloadPath'],
|
|
321
372
|
},
|
|
@@ -334,7 +385,7 @@ export const projectToolDefinitions = [
|
|
|
334
385
|
},
|
|
335
386
|
{
|
|
336
387
|
name: 'update_autoload',
|
|
337
|
-
description:
|
|
388
|
+
description: "Modify an existing autoload's path or singleton flag. No Godot process required.",
|
|
338
389
|
inputSchema: {
|
|
339
390
|
type: 'object',
|
|
340
391
|
properties: {
|
|
@@ -353,8 +404,15 @@ export const projectToolDefinitions = [
|
|
|
353
404
|
type: 'object',
|
|
354
405
|
properties: {
|
|
355
406
|
projectPath: { type: 'string', description: 'Path to the Godot project directory' },
|
|
356
|
-
maxDepth: {
|
|
357
|
-
|
|
407
|
+
maxDepth: {
|
|
408
|
+
type: 'number',
|
|
409
|
+
description: 'Maximum recursion depth. -1 means unlimited (default: -1)',
|
|
410
|
+
},
|
|
411
|
+
extensions: {
|
|
412
|
+
type: 'array',
|
|
413
|
+
items: { type: 'string' },
|
|
414
|
+
description: 'Filter to only these file extensions (e.g. ["gd", "tscn"]). Omit to include all.',
|
|
415
|
+
},
|
|
358
416
|
},
|
|
359
417
|
required: ['projectPath'],
|
|
360
418
|
},
|
|
@@ -367,7 +425,11 @@ export const projectToolDefinitions = [
|
|
|
367
425
|
properties: {
|
|
368
426
|
projectPath: { type: 'string', description: 'Path to the Godot project directory' },
|
|
369
427
|
pattern: { type: 'string', description: 'Plain-text string to search for' },
|
|
370
|
-
fileTypes: {
|
|
428
|
+
fileTypes: {
|
|
429
|
+
type: 'array',
|
|
430
|
+
items: { type: 'string' },
|
|
431
|
+
description: 'File extensions to search (default: ["gd", "tscn", "cs", "gdshader"])',
|
|
432
|
+
},
|
|
371
433
|
caseSensitive: { type: 'boolean', description: 'Case-sensitive search (default: false)' },
|
|
372
434
|
maxResults: { type: 'number', description: 'Maximum matches to return (default: 100)' },
|
|
373
435
|
},
|
|
@@ -381,7 +443,10 @@ export const projectToolDefinitions = [
|
|
|
381
443
|
type: 'object',
|
|
382
444
|
properties: {
|
|
383
445
|
projectPath: { type: 'string', description: 'Path to the Godot project directory' },
|
|
384
|
-
scenePath: {
|
|
446
|
+
scenePath: {
|
|
447
|
+
type: 'string',
|
|
448
|
+
description: 'Path to the .tscn file relative to the project root (e.g. "scenes/main.tscn")',
|
|
449
|
+
},
|
|
385
450
|
},
|
|
386
451
|
required: ['projectPath', 'scenePath'],
|
|
387
452
|
},
|
|
@@ -393,7 +458,10 @@ export const projectToolDefinitions = [
|
|
|
393
458
|
type: 'object',
|
|
394
459
|
properties: {
|
|
395
460
|
projectPath: { type: 'string', description: 'Path to the Godot project directory' },
|
|
396
|
-
section: {
|
|
461
|
+
section: {
|
|
462
|
+
type: 'string',
|
|
463
|
+
description: 'Filter to a specific INI section (e.g. "display", "application"). Omit for all sections.',
|
|
464
|
+
},
|
|
397
465
|
},
|
|
398
466
|
required: ['projectPath'],
|
|
399
467
|
},
|
|
@@ -401,10 +469,17 @@ export const projectToolDefinitions = [
|
|
|
401
469
|
];
|
|
402
470
|
function ensureRuntimeSession(runner, actionDescription) {
|
|
403
471
|
if (!runner.activeSessionMode || !runner.activeProjectPath) {
|
|
404
|
-
return createErrorResponse(`No active runtime session. A project must be running or attached to ${actionDescription}.`, [
|
|
472
|
+
return createErrorResponse(`No active runtime session. A project must be running or attached to ${actionDescription}.`, [
|
|
473
|
+
'Use run_project to start a Godot project first',
|
|
474
|
+
'Or use attach_project before launching Godot manually',
|
|
475
|
+
]);
|
|
405
476
|
}
|
|
406
|
-
if (runner.activeSessionMode === 'spawned' &&
|
|
407
|
-
|
|
477
|
+
if (runner.activeSessionMode === 'spawned' &&
|
|
478
|
+
(!runner.activeProcess || runner.activeProcess.hasExited)) {
|
|
479
|
+
return createErrorResponse(`The spawned Godot process has exited and cannot ${actionDescription}.`, [
|
|
480
|
+
'Use get_debug_output to inspect the last captured logs',
|
|
481
|
+
'Call stop_project to clean up, then run_project again',
|
|
482
|
+
]);
|
|
408
483
|
}
|
|
409
484
|
return null;
|
|
410
485
|
}
|
|
@@ -481,21 +556,31 @@ function getProjectStructure(projectPath) {
|
|
|
481
556
|
export async function handleLaunchEditor(runner, args) {
|
|
482
557
|
args = normalizeParameters(args);
|
|
483
558
|
if (!args.projectPath) {
|
|
484
|
-
return createErrorResponse('Project path is required', [
|
|
559
|
+
return createErrorResponse('Project path is required', [
|
|
560
|
+
'Provide a valid path to a Godot project directory',
|
|
561
|
+
]);
|
|
485
562
|
}
|
|
486
563
|
if (!validatePath(args.projectPath)) {
|
|
487
|
-
return createErrorResponse('Invalid project path', [
|
|
564
|
+
return createErrorResponse('Invalid project path', [
|
|
565
|
+
'Provide a valid path without ".." or other potentially unsafe characters',
|
|
566
|
+
]);
|
|
488
567
|
}
|
|
489
568
|
try {
|
|
490
569
|
if (!runner.getGodotPath()) {
|
|
491
570
|
await runner.detectGodotPath();
|
|
492
571
|
if (!runner.getGodotPath()) {
|
|
493
|
-
return createErrorResponse('Could not find a valid Godot executable path', [
|
|
572
|
+
return createErrorResponse('Could not find a valid Godot executable path', [
|
|
573
|
+
'Ensure Godot is installed correctly',
|
|
574
|
+
'Set GODOT_PATH environment variable',
|
|
575
|
+
]);
|
|
494
576
|
}
|
|
495
577
|
}
|
|
496
578
|
const projectFile = join(args.projectPath, 'project.godot');
|
|
497
579
|
if (!existsSync(projectFile)) {
|
|
498
|
-
return createErrorResponse(`Not a valid Godot project: ${args.projectPath}`, [
|
|
580
|
+
return createErrorResponse(`Not a valid Godot project: ${args.projectPath}`, [
|
|
581
|
+
'Ensure the path points to a directory containing a project.godot file',
|
|
582
|
+
'Use list_projects to find valid Godot projects',
|
|
583
|
+
]);
|
|
499
584
|
}
|
|
500
585
|
logDebug(`Launching Godot editor for project: ${args.projectPath}`);
|
|
501
586
|
const process = runner.launchEditor(args.projectPath);
|
|
@@ -503,34 +588,75 @@ export async function handleLaunchEditor(runner, args) {
|
|
|
503
588
|
console.error('Failed to start Godot editor:', err);
|
|
504
589
|
});
|
|
505
590
|
return {
|
|
506
|
-
content: [
|
|
591
|
+
content: [
|
|
592
|
+
{
|
|
593
|
+
type: 'text',
|
|
594
|
+
text: `Godot editor launched successfully for project at ${args.projectPath}.\nNote: the editor is a GUI application and cannot be controlled programmatically. Use the scene and node editing tools (add_node, set_node_property, etc.) to modify the project headlessly without the editor.`,
|
|
595
|
+
},
|
|
596
|
+
],
|
|
507
597
|
};
|
|
508
598
|
}
|
|
509
599
|
catch (error) {
|
|
510
600
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
511
|
-
return createErrorResponse(`Failed to launch Godot editor: ${errorMessage}`, [
|
|
601
|
+
return createErrorResponse(`Failed to launch Godot editor: ${errorMessage}`, [
|
|
602
|
+
'Ensure Godot is installed correctly',
|
|
603
|
+
'Check if the GODOT_PATH environment variable is set correctly',
|
|
604
|
+
]);
|
|
512
605
|
}
|
|
513
606
|
}
|
|
514
607
|
export async function handleRunProject(runner, args) {
|
|
515
608
|
args = normalizeParameters(args);
|
|
516
609
|
if (!args.projectPath) {
|
|
517
|
-
return createErrorResponse('Project path is required', [
|
|
610
|
+
return createErrorResponse('Project path is required', [
|
|
611
|
+
'Provide a valid path to a Godot project directory',
|
|
612
|
+
]);
|
|
518
613
|
}
|
|
519
614
|
if (!validatePath(args.projectPath)) {
|
|
520
|
-
return createErrorResponse('Invalid project path', [
|
|
615
|
+
return createErrorResponse('Invalid project path', [
|
|
616
|
+
'Provide a valid path without ".." or other potentially unsafe characters',
|
|
617
|
+
]);
|
|
521
618
|
}
|
|
522
619
|
try {
|
|
523
620
|
const projectFile = join(args.projectPath, 'project.godot');
|
|
524
621
|
if (!existsSync(projectFile)) {
|
|
525
|
-
return createErrorResponse(`Not a valid Godot project: ${args.projectPath}`, [
|
|
622
|
+
return createErrorResponse(`Not a valid Godot project: ${args.projectPath}`, [
|
|
623
|
+
'Ensure the path points to a directory containing a project.godot file',
|
|
624
|
+
'Use list_projects to find valid Godot projects',
|
|
625
|
+
]);
|
|
526
626
|
}
|
|
527
627
|
const background = args.background === true;
|
|
528
628
|
runner.runProject(args.projectPath, args.scene, background);
|
|
629
|
+
const bridgeResult = await runner.waitForBridge();
|
|
630
|
+
if (!bridgeResult.ready) {
|
|
631
|
+
if (runner.activeProcess && runner.activeProcess.hasExited) {
|
|
632
|
+
return createErrorResponse(`Godot process exited before the MCP bridge could initialize.\n${bridgeResult.error || ''}`, [
|
|
633
|
+
'Check get_debug_output for runtime errors',
|
|
634
|
+
'Verify a display server is available (Wayland/X11)',
|
|
635
|
+
'Check for broken autoloads with list_autoloads',
|
|
636
|
+
'Call stop_project to clean up, then try again',
|
|
637
|
+
]);
|
|
638
|
+
}
|
|
639
|
+
const lines = [
|
|
640
|
+
'Godot process started, but the MCP bridge did not respond within 8 seconds.',
|
|
641
|
+
'- The process is running — bridge may still be initializing',
|
|
642
|
+
'- Use get_debug_output to investigate',
|
|
643
|
+
'- Runtime tools may work if you retry after a moment',
|
|
644
|
+
'- Call stop_project when done',
|
|
645
|
+
];
|
|
646
|
+
if (background) {
|
|
647
|
+
lines.push('- Background mode: window hidden, physical input blocked');
|
|
648
|
+
}
|
|
649
|
+
return createErrorResponse(lines.join('\n'), [
|
|
650
|
+
'Use get_debug_output to inspect the last captured logs',
|
|
651
|
+
'Check that UDP port 9900 is not occupied by another Godot process',
|
|
652
|
+
'Call stop_project to clean up, then run_project again',
|
|
653
|
+
]);
|
|
654
|
+
}
|
|
529
655
|
const lines = [
|
|
530
|
-
'Godot project started
|
|
656
|
+
'Godot project started and MCP bridge is ready.',
|
|
657
|
+
'- Runtime tools (take_screenshot, simulate_input, get_ui_elements, run_script) are available now',
|
|
531
658
|
'- Use get_debug_output to check runtime output and errors',
|
|
532
|
-
'-
|
|
533
|
-
'- Always call stop_project when done — it terminates the process and cleans up the MCP bridge',
|
|
659
|
+
'- Call stop_project when done',
|
|
534
660
|
];
|
|
535
661
|
if (background) {
|
|
536
662
|
lines.push('- Background mode: window hidden, physical input blocked');
|
|
@@ -541,64 +667,112 @@ export async function handleRunProject(runner, args) {
|
|
|
541
667
|
}
|
|
542
668
|
catch (error) {
|
|
543
669
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
544
|
-
return createErrorResponse(`Failed to run Godot project: ${errorMessage}`, [
|
|
670
|
+
return createErrorResponse(`Failed to run Godot project: ${errorMessage}`, [
|
|
671
|
+
'Ensure Godot is installed correctly',
|
|
672
|
+
'Check if the GODOT_PATH environment variable is set correctly',
|
|
673
|
+
]);
|
|
545
674
|
}
|
|
546
675
|
}
|
|
547
676
|
export async function handleAttachProject(runner, args) {
|
|
548
677
|
args = normalizeParameters(args);
|
|
549
678
|
if (!args.projectPath) {
|
|
550
|
-
return createErrorResponse('Project path is required', [
|
|
679
|
+
return createErrorResponse('Project path is required', [
|
|
680
|
+
'Provide a valid path to a Godot project directory',
|
|
681
|
+
]);
|
|
551
682
|
}
|
|
552
683
|
if (!validatePath(args.projectPath)) {
|
|
553
|
-
return createErrorResponse('Invalid project path', [
|
|
684
|
+
return createErrorResponse('Invalid project path', [
|
|
685
|
+
'Provide a valid path without ".." or other potentially unsafe characters',
|
|
686
|
+
]);
|
|
554
687
|
}
|
|
555
688
|
try {
|
|
556
689
|
const projectFile = join(args.projectPath, 'project.godot');
|
|
557
690
|
if (!existsSync(projectFile)) {
|
|
558
|
-
return createErrorResponse(`Not a valid Godot project: ${args.projectPath}`, [
|
|
691
|
+
return createErrorResponse(`Not a valid Godot project: ${args.projectPath}`, [
|
|
692
|
+
'Ensure the path points to a directory containing a project.godot file',
|
|
693
|
+
'Use list_projects to find valid Godot projects',
|
|
694
|
+
]);
|
|
559
695
|
}
|
|
560
696
|
runner.attachProject(args.projectPath);
|
|
697
|
+
if (args.waitForBridge === true) {
|
|
698
|
+
const bridgeResult = await runner.waitForBridgeAttached();
|
|
699
|
+
if (!bridgeResult.ready) {
|
|
700
|
+
return createErrorResponse(`Project attached but the MCP bridge is not ready.\n${bridgeResult.error || ''}`, [
|
|
701
|
+
'Verify Godot is running with this project',
|
|
702
|
+
'The McpBridge autoload must be initialized and listening on UDP port 9900',
|
|
703
|
+
'Check that no other Godot project is occupying port 9900',
|
|
704
|
+
'Use detach_project or stop_project when done',
|
|
705
|
+
]);
|
|
706
|
+
}
|
|
707
|
+
return {
|
|
708
|
+
content: [
|
|
709
|
+
{
|
|
710
|
+
type: 'text',
|
|
711
|
+
text: [
|
|
712
|
+
'Project attached and MCP bridge is ready.',
|
|
713
|
+
'- Runtime tools (take_screenshot, simulate_input, get_ui_elements, run_script) are available now',
|
|
714
|
+
'- get_debug_output is unavailable in attached mode because MCP did not spawn the process',
|
|
715
|
+
'- Use detach_project or stop_project when done to clean up the injected bridge state',
|
|
716
|
+
].join('\n'),
|
|
717
|
+
},
|
|
718
|
+
],
|
|
719
|
+
};
|
|
720
|
+
}
|
|
561
721
|
return {
|
|
562
|
-
content: [
|
|
722
|
+
content: [
|
|
723
|
+
{
|
|
563
724
|
type: 'text',
|
|
564
725
|
text: [
|
|
565
726
|
'Project attached for manual runtime use.',
|
|
566
|
-
'- Launch Godot yourself
|
|
567
|
-
'-
|
|
727
|
+
'- Launch Godot yourself, then call attach_project again with waitForBridge: true to confirm readiness',
|
|
728
|
+
'- Or use runtime tools directly — they will fail with a clear error if the bridge is not yet listening',
|
|
568
729
|
'- get_debug_output is unavailable in attached mode because MCP did not spawn the process',
|
|
569
730
|
'- Use detach_project or stop_project when done to clean up the injected bridge state',
|
|
570
731
|
].join('\n'),
|
|
571
|
-
}
|
|
732
|
+
},
|
|
733
|
+
],
|
|
572
734
|
};
|
|
573
735
|
}
|
|
574
736
|
catch (error) {
|
|
575
737
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
576
|
-
return createErrorResponse(`Failed to attach project: ${errorMessage}`, [
|
|
738
|
+
return createErrorResponse(`Failed to attach project: ${errorMessage}`, [
|
|
739
|
+
'Check if project.godot is accessible',
|
|
740
|
+
'Ensure MCP can write the bridge autoload into the project',
|
|
741
|
+
]);
|
|
577
742
|
}
|
|
578
743
|
}
|
|
579
744
|
export function handleDetachProject(runner) {
|
|
580
745
|
if (runner.activeSessionMode !== 'attached') {
|
|
581
|
-
return createErrorResponse('No attached project to detach.', [
|
|
746
|
+
return createErrorResponse('No attached project to detach.', [
|
|
747
|
+
'Use attach_project first for manual-launch workflows',
|
|
748
|
+
'If MCP launched the game, use stop_project instead',
|
|
749
|
+
]);
|
|
582
750
|
}
|
|
583
751
|
const result = runner.stopProject();
|
|
584
752
|
return {
|
|
585
|
-
content: [
|
|
753
|
+
content: [
|
|
754
|
+
{
|
|
586
755
|
type: 'text',
|
|
587
756
|
text: JSON.stringify({
|
|
588
757
|
message: 'Detached attached project and cleaned MCP bridge state',
|
|
589
758
|
externalProcessPreserved: result.externalProcessPreserved === true,
|
|
590
759
|
}),
|
|
591
|
-
}
|
|
760
|
+
},
|
|
761
|
+
],
|
|
592
762
|
};
|
|
593
763
|
}
|
|
594
764
|
export function handleGetDebugOutput(runner, args = {}) {
|
|
595
765
|
args = normalizeParameters(args);
|
|
596
766
|
if (!runner.activeSessionMode) {
|
|
597
|
-
return createErrorResponse('No active runtime session.', [
|
|
767
|
+
return createErrorResponse('No active runtime session.', [
|
|
768
|
+
'Use run_project to start a Godot project first',
|
|
769
|
+
'Or use attach_project before launching Godot manually',
|
|
770
|
+
]);
|
|
598
771
|
}
|
|
599
772
|
if (runner.activeSessionMode === 'attached') {
|
|
600
773
|
return {
|
|
601
|
-
content: [
|
|
774
|
+
content: [
|
|
775
|
+
{
|
|
602
776
|
type: 'text',
|
|
603
777
|
text: JSON.stringify({
|
|
604
778
|
output: [],
|
|
@@ -607,12 +781,16 @@ export function handleGetDebugOutput(runner, args = {}) {
|
|
|
607
781
|
attached: true,
|
|
608
782
|
tip: 'Attached mode does not capture stdout/stderr because Godot was launched outside MCP.',
|
|
609
783
|
}),
|
|
610
|
-
}
|
|
784
|
+
},
|
|
785
|
+
],
|
|
611
786
|
};
|
|
612
787
|
}
|
|
613
788
|
const proc = runner.activeProcess;
|
|
614
789
|
if (!proc) {
|
|
615
|
-
return createErrorResponse('No active spawned process is available for debug output.', [
|
|
790
|
+
return createErrorResponse('No active spawned process is available for debug output.', [
|
|
791
|
+
'Use run_project to start a Godot project first',
|
|
792
|
+
'Or use attach_project only when stdout/stderr capture is not needed',
|
|
793
|
+
]);
|
|
616
794
|
}
|
|
617
795
|
const limit = typeof args.limit === 'number' ? args.limit : 200;
|
|
618
796
|
const response = {
|
|
@@ -622,22 +800,29 @@ export function handleGetDebugOutput(runner, args = {}) {
|
|
|
622
800
|
};
|
|
623
801
|
if (proc.hasExited) {
|
|
624
802
|
response.exitCode = proc.exitCode;
|
|
625
|
-
response.tip =
|
|
803
|
+
response.tip =
|
|
804
|
+
'Process has exited. Call stop_project to clean up the process slot before starting a new one.';
|
|
626
805
|
}
|
|
627
806
|
return {
|
|
628
|
-
content: [
|
|
807
|
+
content: [
|
|
808
|
+
{
|
|
629
809
|
type: 'text',
|
|
630
810
|
text: JSON.stringify(response),
|
|
631
|
-
}
|
|
811
|
+
},
|
|
812
|
+
],
|
|
632
813
|
};
|
|
633
814
|
}
|
|
634
815
|
export function handleStopProject(runner) {
|
|
635
816
|
const result = runner.stopProject();
|
|
636
817
|
if (!result) {
|
|
637
|
-
return createErrorResponse('No active Godot process to stop.', [
|
|
818
|
+
return createErrorResponse('No active Godot process to stop.', [
|
|
819
|
+
'Use run_project to start a Godot project first',
|
|
820
|
+
'The process may have already terminated',
|
|
821
|
+
]);
|
|
638
822
|
}
|
|
639
823
|
return {
|
|
640
|
-
content: [
|
|
824
|
+
content: [
|
|
825
|
+
{
|
|
641
826
|
type: 'text',
|
|
642
827
|
text: JSON.stringify({
|
|
643
828
|
message: result.mode === 'attached'
|
|
@@ -648,20 +833,27 @@ export function handleStopProject(runner) {
|
|
|
648
833
|
finalOutput: result.output.slice(-200),
|
|
649
834
|
finalErrors: result.errors.slice(-200),
|
|
650
835
|
}),
|
|
651
|
-
}
|
|
836
|
+
},
|
|
837
|
+
],
|
|
652
838
|
};
|
|
653
839
|
}
|
|
654
840
|
export async function handleListProjects(args) {
|
|
655
841
|
args = normalizeParameters(args);
|
|
656
842
|
if (!args.directory) {
|
|
657
|
-
return createErrorResponse('Directory is required', [
|
|
843
|
+
return createErrorResponse('Directory is required', [
|
|
844
|
+
'Provide a valid directory path to search for Godot projects',
|
|
845
|
+
]);
|
|
658
846
|
}
|
|
659
847
|
if (!validatePath(args.directory)) {
|
|
660
|
-
return createErrorResponse('Invalid directory path', [
|
|
848
|
+
return createErrorResponse('Invalid directory path', [
|
|
849
|
+
'Provide a valid path without ".." or other potentially unsafe characters',
|
|
850
|
+
]);
|
|
661
851
|
}
|
|
662
852
|
try {
|
|
663
853
|
if (!existsSync(args.directory)) {
|
|
664
|
-
return createErrorResponse(`Directory does not exist: ${args.directory}`, [
|
|
854
|
+
return createErrorResponse(`Directory does not exist: ${args.directory}`, [
|
|
855
|
+
'Provide a valid directory path that exists on the system',
|
|
856
|
+
]);
|
|
665
857
|
}
|
|
666
858
|
const recursive = args.recursive === true;
|
|
667
859
|
const projects = findGodotProjects(args.directory, recursive);
|
|
@@ -671,7 +863,10 @@ export async function handleListProjects(args) {
|
|
|
671
863
|
}
|
|
672
864
|
catch (error) {
|
|
673
865
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
674
|
-
return createErrorResponse(`Failed to list projects: ${errorMessage}`, [
|
|
866
|
+
return createErrorResponse(`Failed to list projects: ${errorMessage}`, [
|
|
867
|
+
'Ensure the directory exists and is accessible',
|
|
868
|
+
'Check if you have permission to read the directory',
|
|
869
|
+
]);
|
|
675
870
|
}
|
|
676
871
|
}
|
|
677
872
|
export async function handleGetProjectInfo(runner, args) {
|
|
@@ -685,11 +880,16 @@ export async function handleGetProjectInfo(runner, args) {
|
|
|
685
880
|
};
|
|
686
881
|
}
|
|
687
882
|
if (!validatePath(args.projectPath)) {
|
|
688
|
-
return createErrorResponse('Invalid project path', [
|
|
883
|
+
return createErrorResponse('Invalid project path', [
|
|
884
|
+
'Provide a valid path without ".." or other potentially unsafe characters',
|
|
885
|
+
]);
|
|
689
886
|
}
|
|
690
887
|
const projectFile = join(args.projectPath, 'project.godot');
|
|
691
888
|
if (!existsSync(projectFile)) {
|
|
692
|
-
return createErrorResponse(`Not a valid Godot project: ${args.projectPath}`, [
|
|
889
|
+
return createErrorResponse(`Not a valid Godot project: ${args.projectPath}`, [
|
|
890
|
+
'Ensure the path points to a directory containing a project.godot file',
|
|
891
|
+
'Use list_projects to find valid Godot projects',
|
|
892
|
+
]);
|
|
693
893
|
}
|
|
694
894
|
const projectStructure = getProjectStructure(args.projectPath);
|
|
695
895
|
let projectName = basename(args.projectPath);
|
|
@@ -705,7 +905,8 @@ export async function handleGetProjectInfo(runner, args) {
|
|
|
705
905
|
logDebug(`Error reading project file: ${error}`);
|
|
706
906
|
}
|
|
707
907
|
return {
|
|
708
|
-
content: [
|
|
908
|
+
content: [
|
|
909
|
+
{
|
|
709
910
|
type: 'text',
|
|
710
911
|
text: JSON.stringify({
|
|
711
912
|
name: projectName,
|
|
@@ -713,12 +914,16 @@ export async function handleGetProjectInfo(runner, args) {
|
|
|
713
914
|
godotVersion: version,
|
|
714
915
|
structure: projectStructure,
|
|
715
916
|
}),
|
|
716
|
-
}
|
|
917
|
+
},
|
|
918
|
+
],
|
|
717
919
|
};
|
|
718
920
|
}
|
|
719
921
|
catch (error) {
|
|
720
922
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
721
|
-
return createErrorResponse(`Failed to get project info: ${errorMessage}`, [
|
|
923
|
+
return createErrorResponse(`Failed to get project info: ${errorMessage}`, [
|
|
924
|
+
'Ensure Godot is installed correctly',
|
|
925
|
+
'Check if the GODOT_PATH environment variable is set correctly',
|
|
926
|
+
]);
|
|
722
927
|
}
|
|
723
928
|
}
|
|
724
929
|
export async function handleTakeScreenshot(runner, args) {
|
|
@@ -729,46 +934,64 @@ export async function handleTakeScreenshot(runner, args) {
|
|
|
729
934
|
}
|
|
730
935
|
const timeout = typeof args.timeout === 'number' ? args.timeout : 10000;
|
|
731
936
|
try {
|
|
732
|
-
const responseStr = await runner.
|
|
937
|
+
const { response: responseStr, runtimeErrors } = await runner.sendCommandWithErrors('screenshot', {}, timeout);
|
|
733
938
|
let parsed;
|
|
734
939
|
try {
|
|
735
940
|
parsed = JSON.parse(responseStr);
|
|
736
941
|
}
|
|
737
942
|
catch {
|
|
738
|
-
return createErrorResponse(`Invalid response from screenshot server: ${responseStr}`, [
|
|
943
|
+
return createErrorResponse(`Invalid response from screenshot server: ${responseStr}`, [
|
|
944
|
+
'The game may not have fully initialized yet',
|
|
945
|
+
'Try again after a few seconds',
|
|
946
|
+
]);
|
|
739
947
|
}
|
|
740
948
|
if (parsed.error) {
|
|
741
|
-
return createErrorResponse(`Screenshot server error: ${parsed.error}`, [
|
|
949
|
+
return createErrorResponse(`Screenshot server error: ${parsed.error}`, [
|
|
950
|
+
'Ensure the game viewport is active',
|
|
951
|
+
'Try again after a moment',
|
|
952
|
+
]);
|
|
742
953
|
}
|
|
743
954
|
if (!parsed.path) {
|
|
744
|
-
return createErrorResponse('Screenshot server returned no file path', [
|
|
955
|
+
return createErrorResponse('Screenshot server returned no file path', [
|
|
956
|
+
'Try again after a few seconds',
|
|
957
|
+
]);
|
|
745
958
|
}
|
|
746
959
|
// Normalize path for the local filesystem (forward slashes from GDScript)
|
|
747
960
|
const screenshotPath = sep === '\\' ? parsed.path.replace(/\//g, '\\') : parsed.path;
|
|
748
961
|
if (!existsSync(screenshotPath)) {
|
|
749
|
-
return createErrorResponse(`Screenshot file not found at: ${screenshotPath}`, [
|
|
962
|
+
return createErrorResponse(`Screenshot file not found at: ${screenshotPath}`, [
|
|
963
|
+
'The screenshot may have failed to save',
|
|
964
|
+
'Check disk space and permissions',
|
|
965
|
+
]);
|
|
750
966
|
}
|
|
751
967
|
const imageBuffer = readFileSync(screenshotPath);
|
|
752
968
|
const base64Data = imageBuffer.toString('base64');
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
969
|
+
const content = [
|
|
970
|
+
{
|
|
971
|
+
type: 'image',
|
|
972
|
+
data: base64Data,
|
|
973
|
+
mimeType: 'image/png',
|
|
974
|
+
},
|
|
975
|
+
{
|
|
976
|
+
type: 'text',
|
|
977
|
+
text: `Screenshot saved to: ${parsed.path}`,
|
|
978
|
+
},
|
|
979
|
+
];
|
|
980
|
+
if (runtimeErrors.length > 0) {
|
|
981
|
+
content.push({
|
|
982
|
+
type: 'text',
|
|
983
|
+
text: JSON.stringify({
|
|
984
|
+
warnings: runtimeErrors.slice(0, MAX_RUNTIME_ERROR_CONTEXT_LINES),
|
|
985
|
+
}),
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
return { content };
|
|
766
989
|
}
|
|
767
990
|
catch (error) {
|
|
768
991
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
769
992
|
return createErrorResponse(`Failed to take screenshot: ${errorMessage}`, [
|
|
770
993
|
'Ensure the project is running (use run_project first)',
|
|
771
|
-
'
|
|
994
|
+
'The bridge may not be ready yet — use get_debug_output to investigate',
|
|
772
995
|
'Check that UDP port 9900 is not blocked',
|
|
773
996
|
]);
|
|
774
997
|
}
|
|
@@ -781,44 +1004,61 @@ export async function handleSimulateInput(runner, args) {
|
|
|
781
1004
|
}
|
|
782
1005
|
const actions = args.actions;
|
|
783
1006
|
if (!Array.isArray(actions) || actions.length === 0) {
|
|
784
|
-
return createErrorResponse('actions must be a non-empty array of input actions', [
|
|
1007
|
+
return createErrorResponse('actions must be a non-empty array of input actions', [
|
|
1008
|
+
'Provide at least one action object with a "type" field',
|
|
1009
|
+
]);
|
|
785
1010
|
}
|
|
786
1011
|
// Calculate timeout: sum of all wait durations + 10s buffer
|
|
787
1012
|
let totalWaitMs = 0;
|
|
788
1013
|
for (const action of actions) {
|
|
789
|
-
if (typeof action === 'object' &&
|
|
1014
|
+
if (typeof action === 'object' &&
|
|
1015
|
+
action !== null &&
|
|
1016
|
+
action.type === 'wait' &&
|
|
1017
|
+
typeof action.ms === 'number') {
|
|
790
1018
|
totalWaitMs += action.ms;
|
|
791
1019
|
}
|
|
792
1020
|
}
|
|
793
1021
|
const timeoutMs = totalWaitMs + 10000;
|
|
794
1022
|
try {
|
|
795
|
-
const responseStr = await runner.
|
|
1023
|
+
const { response: responseStr, runtimeErrors } = await runner.sendCommandWithErrors('input', { actions }, timeoutMs);
|
|
796
1024
|
let parsed;
|
|
797
1025
|
try {
|
|
798
1026
|
parsed = JSON.parse(responseStr);
|
|
799
1027
|
}
|
|
800
1028
|
catch {
|
|
801
|
-
return createErrorResponse(`Invalid response from bridge: ${responseStr}`, [
|
|
1029
|
+
return createErrorResponse(`Invalid response from bridge: ${responseStr}`, [
|
|
1030
|
+
'The game may not have fully initialized yet',
|
|
1031
|
+
'Try again after a few seconds',
|
|
1032
|
+
]);
|
|
802
1033
|
}
|
|
803
1034
|
if (parsed.error) {
|
|
804
|
-
return createErrorResponse(`Input simulation error: ${parsed.error}`, [
|
|
1035
|
+
return createErrorResponse(`Input simulation error: ${parsed.error}`, [
|
|
1036
|
+
'Check action types and parameters',
|
|
1037
|
+
'Ensure key names are valid Godot key names',
|
|
1038
|
+
]);
|
|
1039
|
+
}
|
|
1040
|
+
const payload = {
|
|
1041
|
+
success: true,
|
|
1042
|
+
actions_processed: parsed.actions_processed,
|
|
1043
|
+
tip: 'Call take_screenshot to verify the input had the intended visual effect.',
|
|
1044
|
+
};
|
|
1045
|
+
if (runtimeErrors.length > 0) {
|
|
1046
|
+
payload.warnings = runtimeErrors.slice(0, MAX_RUNTIME_ERROR_CONTEXT_LINES);
|
|
805
1047
|
}
|
|
806
1048
|
return {
|
|
807
|
-
content: [
|
|
1049
|
+
content: [
|
|
1050
|
+
{
|
|
808
1051
|
type: 'text',
|
|
809
|
-
text: JSON.stringify(
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
tip: 'Call take_screenshot to verify the input had the intended visual effect.',
|
|
813
|
-
}),
|
|
814
|
-
}],
|
|
1052
|
+
text: JSON.stringify(payload),
|
|
1053
|
+
},
|
|
1054
|
+
],
|
|
815
1055
|
};
|
|
816
1056
|
}
|
|
817
1057
|
catch (error) {
|
|
818
1058
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
819
1059
|
return createErrorResponse(`Failed to simulate input: ${errorMessage}`, [
|
|
820
1060
|
'Ensure the project is running (use run_project first)',
|
|
821
|
-
'
|
|
1061
|
+
'The bridge may not be ready yet — use get_debug_output to investigate',
|
|
822
1062
|
'Check that UDP port 9900 is not blocked',
|
|
823
1063
|
]);
|
|
824
1064
|
}
|
|
@@ -834,32 +1074,43 @@ export async function handleGetUiElements(runner, args) {
|
|
|
834
1074
|
const cmdParams = { visible_only: visibleOnly };
|
|
835
1075
|
if (args.filter)
|
|
836
1076
|
cmdParams.type_filter = args.filter;
|
|
837
|
-
const responseStr = await runner.
|
|
1077
|
+
const { response: responseStr, runtimeErrors } = await runner.sendCommandWithErrors('get_ui_elements', cmdParams);
|
|
838
1078
|
let parsed;
|
|
839
1079
|
try {
|
|
840
1080
|
parsed = JSON.parse(responseStr);
|
|
841
1081
|
}
|
|
842
1082
|
catch {
|
|
843
|
-
return createErrorResponse(`Invalid response from bridge: ${responseStr}`, [
|
|
1083
|
+
return createErrorResponse(`Invalid response from bridge: ${responseStr}`, [
|
|
1084
|
+
'The game may not have fully initialized yet',
|
|
1085
|
+
'Try again after a few seconds',
|
|
1086
|
+
]);
|
|
844
1087
|
}
|
|
845
1088
|
if (parsed.error) {
|
|
846
|
-
return createErrorResponse(`UI element query error: ${parsed.error}`, [
|
|
1089
|
+
return createErrorResponse(`UI element query error: ${parsed.error}`, [
|
|
1090
|
+
'Ensure the game has a UI with Control nodes',
|
|
1091
|
+
]);
|
|
1092
|
+
}
|
|
1093
|
+
const payload = {
|
|
1094
|
+
...parsed,
|
|
1095
|
+
tip: "Use simulate_input with type 'click_element' and a node_path or text value from this list to interact with these elements.",
|
|
1096
|
+
};
|
|
1097
|
+
if (runtimeErrors.length > 0) {
|
|
1098
|
+
payload.warnings = runtimeErrors.slice(0, MAX_RUNTIME_ERROR_CONTEXT_LINES);
|
|
847
1099
|
}
|
|
848
1100
|
return {
|
|
849
|
-
content: [
|
|
1101
|
+
content: [
|
|
1102
|
+
{
|
|
850
1103
|
type: 'text',
|
|
851
|
-
text: JSON.stringify(
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
}),
|
|
855
|
-
}],
|
|
1104
|
+
text: JSON.stringify(payload),
|
|
1105
|
+
},
|
|
1106
|
+
],
|
|
856
1107
|
};
|
|
857
1108
|
}
|
|
858
1109
|
catch (error) {
|
|
859
1110
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
860
1111
|
return createErrorResponse(`Failed to get UI elements: ${errorMessage}`, [
|
|
861
1112
|
'Ensure the project is running (use run_project first)',
|
|
862
|
-
'
|
|
1113
|
+
'The bridge may not be ready yet — use get_debug_output to investigate',
|
|
863
1114
|
'Check that UDP port 9900 is not blocked',
|
|
864
1115
|
]);
|
|
865
1116
|
}
|
|
@@ -872,7 +1123,9 @@ export async function handleRunScript(runner, args) {
|
|
|
872
1123
|
}
|
|
873
1124
|
const script = args.script;
|
|
874
1125
|
if (typeof script !== 'string' || script.trim() === '') {
|
|
875
|
-
return createErrorResponse('script is required and must be a non-empty string', [
|
|
1126
|
+
return createErrorResponse('script is required and must be a non-empty string', [
|
|
1127
|
+
'Provide GDScript source code with extends RefCounted and func execute(scene_tree: SceneTree) -> Variant',
|
|
1128
|
+
]);
|
|
876
1129
|
}
|
|
877
1130
|
if (!script.includes('func execute')) {
|
|
878
1131
|
return createErrorResponse('Script must define func execute(scene_tree: SceneTree) -> Variant', ['Add a func execute(scene_tree: SceneTree) -> Variant method to your script']);
|
|
@@ -894,33 +1147,70 @@ export async function handleRunScript(runner, args) {
|
|
|
894
1147
|
}
|
|
895
1148
|
const timeout = typeof args.timeout === 'number' ? args.timeout : 30000;
|
|
896
1149
|
try {
|
|
897
|
-
const responseStr = await runner.
|
|
1150
|
+
const { response: responseStr, runtimeErrors } = await runner.sendCommandWithErrors('run_script', { source: script }, timeout);
|
|
898
1151
|
let parsed;
|
|
899
1152
|
try {
|
|
900
1153
|
parsed = JSON.parse(responseStr);
|
|
901
1154
|
}
|
|
902
1155
|
catch {
|
|
903
|
-
return createErrorResponse(`Invalid response from bridge: ${responseStr}`, [
|
|
1156
|
+
return createErrorResponse(`Invalid response from bridge: ${responseStr}`, [
|
|
1157
|
+
'The script may have produced non-JSON output',
|
|
1158
|
+
'Check get_debug_output for print() statements',
|
|
1159
|
+
]);
|
|
904
1160
|
}
|
|
905
1161
|
if (parsed.error) {
|
|
906
|
-
return createErrorResponse(`Script execution error: ${parsed.error}`, [
|
|
1162
|
+
return createErrorResponse(`Script execution error: ${parsed.error}`, [
|
|
1163
|
+
'Check your GDScript syntax',
|
|
1164
|
+
'Ensure the script extends RefCounted',
|
|
1165
|
+
'Check get_debug_output for details',
|
|
1166
|
+
]);
|
|
1167
|
+
}
|
|
1168
|
+
// Detect false-positive success: GDScript has no try-catch, so runtime errors
|
|
1169
|
+
// return null and the real error only appears in stderr.
|
|
1170
|
+
if (parsed.success && parsed.result === null && runner.activeSessionMode === 'spawned') {
|
|
1171
|
+
if (runtimeErrors.length > 0) {
|
|
1172
|
+
const errorContext = runtimeErrors.slice(0, MAX_RUNTIME_ERROR_CONTEXT_LINES).join('\n');
|
|
1173
|
+
return createErrorResponse(`Script runtime error detected:\n${errorContext}`, [
|
|
1174
|
+
'Fix the GDScript error in your script and retry',
|
|
1175
|
+
'Use get_debug_output for full process output',
|
|
1176
|
+
]);
|
|
1177
|
+
}
|
|
1178
|
+
return {
|
|
1179
|
+
content: [
|
|
1180
|
+
{
|
|
1181
|
+
type: 'text',
|
|
1182
|
+
text: JSON.stringify({
|
|
1183
|
+
success: true,
|
|
1184
|
+
result: null,
|
|
1185
|
+
warning: 'Script returned null. If unexpected, check get_debug_output for runtime errors — GDScript does not propagate exceptions.',
|
|
1186
|
+
tip: 'Call take_screenshot to verify any visual changes, or get_debug_output to review print() output from your script.',
|
|
1187
|
+
}),
|
|
1188
|
+
},
|
|
1189
|
+
],
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
const payload = {
|
|
1193
|
+
success: true,
|
|
1194
|
+
result: parsed.result,
|
|
1195
|
+
tip: 'Call take_screenshot to verify any visual changes, or get_debug_output to review print() output from your script.',
|
|
1196
|
+
};
|
|
1197
|
+
if (runtimeErrors.length > 0) {
|
|
1198
|
+
payload.warnings = runtimeErrors.slice(0, MAX_RUNTIME_ERROR_CONTEXT_LINES);
|
|
907
1199
|
}
|
|
908
1200
|
return {
|
|
909
|
-
content: [
|
|
1201
|
+
content: [
|
|
1202
|
+
{
|
|
910
1203
|
type: 'text',
|
|
911
|
-
text: JSON.stringify(
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
tip: 'Call take_screenshot to verify any visual changes, or get_debug_output to review print() output from your script.',
|
|
915
|
-
}),
|
|
916
|
-
}],
|
|
1204
|
+
text: JSON.stringify(payload),
|
|
1205
|
+
},
|
|
1206
|
+
],
|
|
917
1207
|
};
|
|
918
1208
|
}
|
|
919
1209
|
catch (error) {
|
|
920
1210
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
921
1211
|
return createErrorResponse(`Failed to execute script: ${errorMessage}`, [
|
|
922
1212
|
'Ensure the project is running (use run_project first)',
|
|
923
|
-
'
|
|
1213
|
+
'The bridge may not be ready yet — wait 2-3 seconds after starting, then check get_debug_output if the issue persists',
|
|
924
1214
|
'Check that UDP port 9900 is not blocked',
|
|
925
1215
|
'For long-running scripts, increase the timeout parameter',
|
|
926
1216
|
]);
|
|
@@ -1061,7 +1351,9 @@ export async function handleListAutoloads(args) {
|
|
|
1061
1351
|
}
|
|
1062
1352
|
catch (error) {
|
|
1063
1353
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1064
|
-
return createErrorResponse(`Failed to list autoloads: ${errorMessage}`, [
|
|
1354
|
+
return createErrorResponse(`Failed to list autoloads: ${errorMessage}`, [
|
|
1355
|
+
'Check if project.godot is accessible',
|
|
1356
|
+
]);
|
|
1065
1357
|
}
|
|
1066
1358
|
}
|
|
1067
1359
|
export async function handleAddAutoload(args) {
|
|
@@ -1070,7 +1362,9 @@ export async function handleAddAutoload(args) {
|
|
|
1070
1362
|
if ('isError' in v)
|
|
1071
1363
|
return v;
|
|
1072
1364
|
if (!args.autoloadName || !args.autoloadPath) {
|
|
1073
|
-
return createErrorResponse('autoloadName and autoloadPath are required', [
|
|
1365
|
+
return createErrorResponse('autoloadName and autoloadPath are required', [
|
|
1366
|
+
'Provide the autoload node name and script/scene path',
|
|
1367
|
+
]);
|
|
1074
1368
|
}
|
|
1075
1369
|
if (!validatePath(args.autoloadPath)) {
|
|
1076
1370
|
return createErrorResponse('Invalid autoload path', ['Provide a valid path without ".."']);
|
|
@@ -1078,18 +1372,28 @@ export async function handleAddAutoload(args) {
|
|
|
1078
1372
|
try {
|
|
1079
1373
|
const projectFile = join(v.projectPath, 'project.godot');
|
|
1080
1374
|
const existing = parseAutoloads(projectFile);
|
|
1081
|
-
if (existing.some(a => a.name === args.autoloadName)) {
|
|
1082
|
-
return createErrorResponse(`Autoload '${args.autoloadName}' already exists`, [
|
|
1375
|
+
if (existing.some((a) => a.name === args.autoloadName)) {
|
|
1376
|
+
return createErrorResponse(`Autoload '${args.autoloadName}' already exists`, [
|
|
1377
|
+
'Use update_autoload to modify it',
|
|
1378
|
+
'Use list_autoloads to see current autoloads',
|
|
1379
|
+
]);
|
|
1083
1380
|
}
|
|
1084
1381
|
const isSingleton = args.singleton !== false;
|
|
1085
1382
|
addAutoloadEntry(projectFile, args.autoloadName, args.autoloadPath, isSingleton);
|
|
1086
1383
|
return {
|
|
1087
|
-
content: [
|
|
1384
|
+
content: [
|
|
1385
|
+
{
|
|
1386
|
+
type: 'text',
|
|
1387
|
+
text: `Autoload '${args.autoloadName}' registered at '${args.autoloadPath}' (singleton: ${isSingleton}).\nWarning: autoloads initialize in headless mode too. If this script has errors, all headless operations will fail. Verify by running get_scene_tree — if it fails, use remove_autoload to remove it.`,
|
|
1388
|
+
},
|
|
1389
|
+
],
|
|
1088
1390
|
};
|
|
1089
1391
|
}
|
|
1090
1392
|
catch (error) {
|
|
1091
1393
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1092
|
-
return createErrorResponse(`Failed to add autoload: ${errorMessage}`, [
|
|
1394
|
+
return createErrorResponse(`Failed to add autoload: ${errorMessage}`, [
|
|
1395
|
+
'Check if project.godot is accessible',
|
|
1396
|
+
]);
|
|
1093
1397
|
}
|
|
1094
1398
|
}
|
|
1095
1399
|
export async function handleRemoveAutoload(args) {
|
|
@@ -1098,19 +1402,27 @@ export async function handleRemoveAutoload(args) {
|
|
|
1098
1402
|
if ('isError' in v)
|
|
1099
1403
|
return v;
|
|
1100
1404
|
if (!args.autoloadName) {
|
|
1101
|
-
return createErrorResponse('autoloadName is required', [
|
|
1405
|
+
return createErrorResponse('autoloadName is required', [
|
|
1406
|
+
'Provide the name of the autoload to remove',
|
|
1407
|
+
]);
|
|
1102
1408
|
}
|
|
1103
1409
|
try {
|
|
1104
1410
|
const projectFile = join(v.projectPath, 'project.godot');
|
|
1105
1411
|
const removed = removeAutoloadEntry(projectFile, args.autoloadName);
|
|
1106
1412
|
if (!removed) {
|
|
1107
|
-
return createErrorResponse(`Autoload '${args.autoloadName}' not found`, [
|
|
1413
|
+
return createErrorResponse(`Autoload '${args.autoloadName}' not found`, [
|
|
1414
|
+
'Use list_autoloads to see existing autoloads',
|
|
1415
|
+
]);
|
|
1108
1416
|
}
|
|
1109
|
-
return {
|
|
1417
|
+
return {
|
|
1418
|
+
content: [{ type: 'text', text: `Autoload '${args.autoloadName}' removed successfully.` }],
|
|
1419
|
+
};
|
|
1110
1420
|
}
|
|
1111
1421
|
catch (error) {
|
|
1112
1422
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1113
|
-
return createErrorResponse(`Failed to remove autoload: ${errorMessage}`, [
|
|
1423
|
+
return createErrorResponse(`Failed to remove autoload: ${errorMessage}`, [
|
|
1424
|
+
'Check if project.godot is accessible',
|
|
1425
|
+
]);
|
|
1114
1426
|
}
|
|
1115
1427
|
}
|
|
1116
1428
|
export async function handleUpdateAutoload(args) {
|
|
@@ -1119,7 +1431,9 @@ export async function handleUpdateAutoload(args) {
|
|
|
1119
1431
|
if ('isError' in v)
|
|
1120
1432
|
return v;
|
|
1121
1433
|
if (!args.autoloadName) {
|
|
1122
|
-
return createErrorResponse('autoloadName is required', [
|
|
1434
|
+
return createErrorResponse('autoloadName is required', [
|
|
1435
|
+
'Provide the name of the autoload to update',
|
|
1436
|
+
]);
|
|
1123
1437
|
}
|
|
1124
1438
|
if (args.autoloadPath && !validatePath(args.autoloadPath)) {
|
|
1125
1439
|
return createErrorResponse('Invalid autoload path', ['Provide a valid path without ".."']);
|
|
@@ -1128,13 +1442,20 @@ export async function handleUpdateAutoload(args) {
|
|
|
1128
1442
|
const projectFile = join(v.projectPath, 'project.godot');
|
|
1129
1443
|
const updated = updateAutoloadEntry(projectFile, args.autoloadName, args.autoloadPath, args.singleton);
|
|
1130
1444
|
if (!updated) {
|
|
1131
|
-
return createErrorResponse(`Autoload '${args.autoloadName}' not found`, [
|
|
1445
|
+
return createErrorResponse(`Autoload '${args.autoloadName}' not found`, [
|
|
1446
|
+
'Use list_autoloads to see existing autoloads',
|
|
1447
|
+
'Use add_autoload to register a new one',
|
|
1448
|
+
]);
|
|
1132
1449
|
}
|
|
1133
|
-
return {
|
|
1450
|
+
return {
|
|
1451
|
+
content: [{ type: 'text', text: `Autoload '${args.autoloadName}' updated successfully.` }],
|
|
1452
|
+
};
|
|
1134
1453
|
}
|
|
1135
1454
|
catch (error) {
|
|
1136
1455
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1137
|
-
return createErrorResponse(`Failed to update autoload: ${errorMessage}`, [
|
|
1456
|
+
return createErrorResponse(`Failed to update autoload: ${errorMessage}`, [
|
|
1457
|
+
'Check if project.godot is accessible',
|
|
1458
|
+
]);
|
|
1138
1459
|
}
|
|
1139
1460
|
}
|
|
1140
1461
|
export async function handleGetProjectFiles(args) {
|
|
@@ -1145,14 +1466,16 @@ export async function handleGetProjectFiles(args) {
|
|
|
1145
1466
|
try {
|
|
1146
1467
|
const maxDepth = typeof args.maxDepth === 'number' ? args.maxDepth : -1;
|
|
1147
1468
|
const extensions = Array.isArray(args.extensions)
|
|
1148
|
-
? args.extensions.map(e => e.toLowerCase().replace(/^\./, ''))
|
|
1469
|
+
? args.extensions.map((e) => e.toLowerCase().replace(/^\./, ''))
|
|
1149
1470
|
: null;
|
|
1150
1471
|
const tree = buildFilesystemTree(v.projectPath, '', maxDepth, 0, extensions);
|
|
1151
1472
|
return { content: [{ type: 'text', text: JSON.stringify(tree) }] };
|
|
1152
1473
|
}
|
|
1153
1474
|
catch (error) {
|
|
1154
1475
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1155
|
-
return createErrorResponse(`Failed to get project files: ${errorMessage}`, [
|
|
1476
|
+
return createErrorResponse(`Failed to get project files: ${errorMessage}`, [
|
|
1477
|
+
'Check if the project directory is accessible',
|
|
1478
|
+
]);
|
|
1156
1479
|
}
|
|
1157
1480
|
}
|
|
1158
1481
|
export async function handleSearchProject(args) {
|
|
@@ -1165,7 +1488,7 @@ export async function handleSearchProject(args) {
|
|
|
1165
1488
|
}
|
|
1166
1489
|
try {
|
|
1167
1490
|
const fileTypes = Array.isArray(args.fileTypes)
|
|
1168
|
-
? args.fileTypes.map(e => e.toLowerCase().replace(/^\./, ''))
|
|
1491
|
+
? args.fileTypes.map((e) => e.toLowerCase().replace(/^\./, ''))
|
|
1169
1492
|
: ['gd', 'tscn', 'cs', 'gdshader'];
|
|
1170
1493
|
const caseSensitive = args.caseSensitive === true;
|
|
1171
1494
|
const maxResults = typeof args.maxResults === 'number' ? args.maxResults : 100;
|
|
@@ -1174,7 +1497,9 @@ export async function handleSearchProject(args) {
|
|
|
1174
1497
|
}
|
|
1175
1498
|
catch (error) {
|
|
1176
1499
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1177
|
-
return createErrorResponse(`Failed to search project: ${errorMessage}`, [
|
|
1500
|
+
return createErrorResponse(`Failed to search project: ${errorMessage}`, [
|
|
1501
|
+
'Check if the project directory is accessible',
|
|
1502
|
+
]);
|
|
1178
1503
|
}
|
|
1179
1504
|
}
|
|
1180
1505
|
export async function handleGetSceneDependencies(args) {
|
|
@@ -1183,7 +1508,9 @@ export async function handleGetSceneDependencies(args) {
|
|
|
1183
1508
|
if ('isError' in v)
|
|
1184
1509
|
return v;
|
|
1185
1510
|
if (!args.scenePath || typeof args.scenePath !== 'string') {
|
|
1186
|
-
return createErrorResponse('scenePath is required', [
|
|
1511
|
+
return createErrorResponse('scenePath is required', [
|
|
1512
|
+
'Provide a path relative to the project root, e.g. "scenes/main.tscn"',
|
|
1513
|
+
]);
|
|
1187
1514
|
}
|
|
1188
1515
|
if (!validatePath(args.scenePath)) {
|
|
1189
1516
|
return createErrorResponse('Invalid scenePath', ['Provide a valid path without ".."']);
|
|
@@ -1191,7 +1518,10 @@ export async function handleGetSceneDependencies(args) {
|
|
|
1191
1518
|
try {
|
|
1192
1519
|
const sceneFullPath = join(v.projectPath, args.scenePath);
|
|
1193
1520
|
if (!existsSync(sceneFullPath)) {
|
|
1194
|
-
return createErrorResponse(`Scene file not found: ${args.scenePath}`, [
|
|
1521
|
+
return createErrorResponse(`Scene file not found: ${args.scenePath}`, [
|
|
1522
|
+
'Verify the path is relative to the project root',
|
|
1523
|
+
'Use get_project_files to list available .tscn files',
|
|
1524
|
+
]);
|
|
1195
1525
|
}
|
|
1196
1526
|
const sceneContent = readFileSync(sceneFullPath, 'utf8');
|
|
1197
1527
|
const dependencies = [];
|
|
@@ -1213,11 +1543,15 @@ export async function handleGetSceneDependencies(args) {
|
|
|
1213
1543
|
dependencies.push(dep);
|
|
1214
1544
|
}
|
|
1215
1545
|
}
|
|
1216
|
-
return {
|
|
1546
|
+
return {
|
|
1547
|
+
content: [{ type: 'text', text: JSON.stringify({ scene: args.scenePath, dependencies }) }],
|
|
1548
|
+
};
|
|
1217
1549
|
}
|
|
1218
1550
|
catch (error) {
|
|
1219
1551
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1220
|
-
return createErrorResponse(`Failed to get scene dependencies: ${errorMessage}`, [
|
|
1552
|
+
return createErrorResponse(`Failed to get scene dependencies: ${errorMessage}`, [
|
|
1553
|
+
'Check if the scene file is accessible',
|
|
1554
|
+
]);
|
|
1221
1555
|
}
|
|
1222
1556
|
}
|
|
1223
1557
|
export async function handleGetProjectSettings(args) {
|
|
@@ -1236,7 +1570,9 @@ export async function handleGetProjectSettings(args) {
|
|
|
1236
1570
|
}
|
|
1237
1571
|
catch (error) {
|
|
1238
1572
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1239
|
-
return createErrorResponse(`Failed to get project settings: ${errorMessage}`, [
|
|
1573
|
+
return createErrorResponse(`Failed to get project settings: ${errorMessage}`, [
|
|
1574
|
+
'Check if project.godot is accessible',
|
|
1575
|
+
]);
|
|
1240
1576
|
}
|
|
1241
1577
|
}
|
|
1242
1578
|
//# sourceMappingURL=project-tools.js.map
|