godot-mcp-runtime 1.1.0 → 2.0.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.
- package/README.md +36 -46
- package/dist/index.js +71 -10
- package/dist/index.js.map +1 -1
- package/dist/tools/node-tools.d.ts +121 -1
- package/dist/tools/node-tools.d.ts.map +1 -1
- package/dist/tools/node-tools.js +437 -277
- package/dist/tools/node-tools.js.map +1 -1
- package/dist/tools/project-tools.d.ts +85 -1
- package/dist/tools/project-tools.d.ts.map +1 -1
- package/dist/tools/project-tools.js +276 -192
- package/dist/tools/project-tools.js.map +1 -1
- package/dist/tools/scene-tools.d.ts +61 -1
- package/dist/tools/scene-tools.d.ts.map +1 -1
- package/dist/tools/scene-tools.js +246 -210
- package/dist/tools/scene-tools.js.map +1 -1
- package/dist/utils/godot-runner.d.ts +14 -0
- package/dist/utils/godot-runner.d.ts.map +1 -1
- package/dist/utils/godot-runner.js +59 -1
- package/dist/utils/godot-runner.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join, basename, sep } from 'path';
|
|
2
2
|
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
3
|
-
import { normalizeParameters, validatePath, createErrorResponse, logDebug, } from '../utils/godot-runner.js';
|
|
3
|
+
import { normalizeParameters, validatePath, validateProjectArgs, createErrorResponse, logDebug, } from '../utils/godot-runner.js';
|
|
4
4
|
function parseAutoloads(projectFilePath) {
|
|
5
5
|
const content = readFileSync(projectFilePath, 'utf8');
|
|
6
6
|
const autoloads = [];
|
|
@@ -93,7 +93,7 @@ function updateAutoloadEntry(projectFilePath, name, newPath, singleton) {
|
|
|
93
93
|
export const projectToolDefinitions = [
|
|
94
94
|
{
|
|
95
95
|
name: 'launch_editor',
|
|
96
|
-
description: 'Open the Godot editor GUI for a project. The editor is a display application — it cannot be controlled programmatically and returns immediately. For headless project modification, use
|
|
96
|
+
description: 'Open the Godot editor GUI for a project. The editor is a display application — it cannot be controlled programmatically and returns immediately. For headless project modification, use the scene and node editing tools (add_node, set_node_property, etc.) instead.',
|
|
97
97
|
inputSchema: {
|
|
98
98
|
type: 'object',
|
|
99
99
|
properties: {
|
|
@@ -194,7 +194,7 @@ export const projectToolDefinitions = [
|
|
|
194
194
|
},
|
|
195
195
|
{
|
|
196
196
|
name: 'simulate_input',
|
|
197
|
-
description: 'Simulate batched sequential input in a running Godot project. Requires run_project first; wait 2–3 seconds after starting. 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)',
|
|
197
|
+
description: 'Simulate batched sequential input in a running Godot project. Requires run_project first; wait 2–3 seconds after starting. 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}]',
|
|
198
198
|
inputSchema: {
|
|
199
199
|
type: 'object',
|
|
200
200
|
properties: {
|
|
@@ -269,80 +269,106 @@ export const projectToolDefinitions = [
|
|
|
269
269
|
},
|
|
270
270
|
},
|
|
271
271
|
{
|
|
272
|
-
name: '
|
|
273
|
-
description:
|
|
274
|
-
|
|
275
|
-
⚠️ AUTOLOAD LIMITATION: Never use headless Godot tools (manage_scene, manage_node, etc.) to add or configure autoloads. Running headless initializes ALL existing autoloads — if any are broken or require a display, the process fails. Always use manage_project for autoload management.
|
|
276
|
-
|
|
277
|
-
Operations:
|
|
278
|
-
- list_autoloads: List all registered autoloads with paths and singleton status
|
|
279
|
-
- add_autoload: Register a new autoload (required: autoloadName, autoloadPath; optional: singleton, default true)
|
|
280
|
-
- remove_autoload: Unregister an autoload by name (required: autoloadName)
|
|
281
|
-
- update_autoload: Modify an existing autoload's path or singleton flag (required: autoloadName; optional: autoloadPath, singleton)
|
|
282
|
-
- get_filesystem_tree: Return a recursive file tree of the project (optional: maxDepth, extensions). Skips hidden (dot-prefixed) entries and the .mcp directory. Returns nested { name, type, path, extension?, children? } objects.
|
|
283
|
-
- search_in_files: Plain-text search across project files (required: pattern; optional: fileTypes, caseSensitive, maxResults). Returns { matches: [{ file, lineNumber, line }], truncated } — check truncated if maxResults may have been hit. Skips hidden entries and the .mcp directory.
|
|
284
|
-
- get_scene_dependencies: Parse a .tscn file for ext_resource references only (scripts, textures, subscenes — not inlined sub_resource blocks). Required: scenePath. Returns { scene, dependencies: [{ path, type, uid? }] }.
|
|
285
|
-
- get_project_settings: Parse project.godot into structured JSON (optional: section). Returns { settings: { [section]: { [key]: value } } }. Complex Godot types (e.g. Vector2, PackedStringArray) are returned as raw strings. Keys not under any section appear under __global__.`,
|
|
272
|
+
name: 'list_autoloads',
|
|
273
|
+
description: 'List all registered autoloads in a Godot project with paths and singleton status. No Godot process required — reads project.godot directly.',
|
|
286
274
|
inputSchema: {
|
|
287
275
|
type: 'object',
|
|
288
276
|
properties: {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
},
|
|
302
|
-
autoloadPath: {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
items: { type: 'string' },
|
|
317
|
-
description: '[get_filesystem_tree] Filter results to only these file extensions (e.g. ["gd", "tscn"]). Omit to include all files.',
|
|
318
|
-
},
|
|
319
|
-
pattern: {
|
|
320
|
-
type: 'string',
|
|
321
|
-
description: '[search_in_files] Plain-text string to search for across project files',
|
|
322
|
-
},
|
|
323
|
-
fileTypes: {
|
|
324
|
-
type: 'array',
|
|
325
|
-
items: { type: 'string' },
|
|
326
|
-
description: '[search_in_files] File extensions to search (default: ["gd", "tscn", "cs", "gdshader"])',
|
|
327
|
-
},
|
|
328
|
-
caseSensitive: {
|
|
329
|
-
type: 'boolean',
|
|
330
|
-
description: '[search_in_files] Whether the search is case-sensitive (default: false)',
|
|
331
|
-
},
|
|
332
|
-
maxResults: {
|
|
333
|
-
type: 'number',
|
|
334
|
-
description: '[search_in_files] Maximum number of matches to return (default: 100)',
|
|
335
|
-
},
|
|
336
|
-
scenePath: {
|
|
337
|
-
type: 'string',
|
|
338
|
-
description: '[get_scene_dependencies] Path to the .tscn file relative to the project root (e.g. "scenes/main.tscn")',
|
|
339
|
-
},
|
|
340
|
-
section: {
|
|
341
|
-
type: 'string',
|
|
342
|
-
description: '[get_project_settings] Filter output to a specific INI section (e.g. "display", "application"). Omit to get all sections.',
|
|
343
|
-
},
|
|
277
|
+
projectPath: { type: 'string', description: 'Path to the Godot project directory' },
|
|
278
|
+
},
|
|
279
|
+
required: ['projectPath'],
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: 'add_autoload',
|
|
284
|
+
description: 'Register a new autoload in a Godot project. No Godot process required. Warning: autoloads initialize in headless mode — if the script has errors, all headless operations will fail.',
|
|
285
|
+
inputSchema: {
|
|
286
|
+
type: 'object',
|
|
287
|
+
properties: {
|
|
288
|
+
projectPath: { type: 'string', description: 'Path to the Godot project directory' },
|
|
289
|
+
autoloadName: { type: 'string', description: 'Name of the autoload node (e.g. "MyManager")' },
|
|
290
|
+
autoloadPath: { type: 'string', description: 'Path to the script or scene (e.g. "res://autoload/my_manager.gd" or "autoload/my_manager.gd")' },
|
|
291
|
+
singleton: { type: 'boolean', description: 'Register as a globally accessible singleton by name (default: true)' },
|
|
292
|
+
},
|
|
293
|
+
required: ['projectPath', 'autoloadName', 'autoloadPath'],
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
name: 'remove_autoload',
|
|
298
|
+
description: 'Unregister an autoload from a Godot project by name. No Godot process required.',
|
|
299
|
+
inputSchema: {
|
|
300
|
+
type: 'object',
|
|
301
|
+
properties: {
|
|
302
|
+
projectPath: { type: 'string', description: 'Path to the Godot project directory' },
|
|
303
|
+
autoloadName: { type: 'string', description: 'Name of the autoload to remove' },
|
|
344
304
|
},
|
|
345
|
-
required: ['
|
|
305
|
+
required: ['projectPath', 'autoloadName'],
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
name: 'update_autoload',
|
|
310
|
+
description: 'Modify an existing autoload\'s path or singleton flag. No Godot process required.',
|
|
311
|
+
inputSchema: {
|
|
312
|
+
type: 'object',
|
|
313
|
+
properties: {
|
|
314
|
+
projectPath: { type: 'string', description: 'Path to the Godot project directory' },
|
|
315
|
+
autoloadName: { type: 'string', description: 'Name of the autoload to update' },
|
|
316
|
+
autoloadPath: { type: 'string', description: 'New path to the script or scene' },
|
|
317
|
+
singleton: { type: 'boolean', description: 'New singleton flag' },
|
|
318
|
+
},
|
|
319
|
+
required: ['projectPath', 'autoloadName'],
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
name: 'get_project_files',
|
|
324
|
+
description: 'Return a recursive file tree of a Godot project. Skips hidden (dot-prefixed) entries and the .mcp directory. Returns nested { name, type, path, extension?, children? } objects.',
|
|
325
|
+
inputSchema: {
|
|
326
|
+
type: 'object',
|
|
327
|
+
properties: {
|
|
328
|
+
projectPath: { type: 'string', description: 'Path to the Godot project directory' },
|
|
329
|
+
maxDepth: { type: 'number', description: 'Maximum recursion depth. -1 means unlimited (default: -1)' },
|
|
330
|
+
extensions: { type: 'array', items: { type: 'string' }, description: 'Filter to only these file extensions (e.g. ["gd", "tscn"]). Omit to include all.' },
|
|
331
|
+
},
|
|
332
|
+
required: ['projectPath'],
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
name: 'search_project',
|
|
337
|
+
description: 'Plain-text search across project files. Returns { matches: [{ file, lineNumber, line }], truncated }. Skips hidden entries and the .mcp directory.',
|
|
338
|
+
inputSchema: {
|
|
339
|
+
type: 'object',
|
|
340
|
+
properties: {
|
|
341
|
+
projectPath: { type: 'string', description: 'Path to the Godot project directory' },
|
|
342
|
+
pattern: { type: 'string', description: 'Plain-text string to search for' },
|
|
343
|
+
fileTypes: { type: 'array', items: { type: 'string' }, description: 'File extensions to search (default: ["gd", "tscn", "cs", "gdshader"])' },
|
|
344
|
+
caseSensitive: { type: 'boolean', description: 'Case-sensitive search (default: false)' },
|
|
345
|
+
maxResults: { type: 'number', description: 'Maximum matches to return (default: 100)' },
|
|
346
|
+
},
|
|
347
|
+
required: ['projectPath', 'pattern'],
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: 'get_scene_dependencies',
|
|
352
|
+
description: 'Parse a .tscn file for ext_resource references (scripts, textures, subscenes). Returns { scene, dependencies: [{ path, type, uid? }] }.',
|
|
353
|
+
inputSchema: {
|
|
354
|
+
type: 'object',
|
|
355
|
+
properties: {
|
|
356
|
+
projectPath: { type: 'string', description: 'Path to the Godot project directory' },
|
|
357
|
+
scenePath: { type: 'string', description: 'Path to the .tscn file relative to the project root (e.g. "scenes/main.tscn")' },
|
|
358
|
+
},
|
|
359
|
+
required: ['projectPath', 'scenePath'],
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
name: 'get_project_settings',
|
|
364
|
+
description: 'Parse project.godot into structured JSON. Returns { settings: { [section]: { [key]: value } } }. Complex Godot types are returned as raw strings. Keys not under any section appear under __global__.',
|
|
365
|
+
inputSchema: {
|
|
366
|
+
type: 'object',
|
|
367
|
+
properties: {
|
|
368
|
+
projectPath: { type: 'string', description: 'Path to the Godot project directory' },
|
|
369
|
+
section: { type: 'string', description: 'Filter to a specific INI section (e.g. "display", "application"). Omit for all sections.' },
|
|
370
|
+
},
|
|
371
|
+
required: ['projectPath'],
|
|
346
372
|
},
|
|
347
373
|
},
|
|
348
374
|
];
|
|
@@ -441,7 +467,7 @@ export async function handleLaunchEditor(runner, args) {
|
|
|
441
467
|
console.error('Failed to start Godot editor:', err);
|
|
442
468
|
});
|
|
443
469
|
return {
|
|
444
|
-
content: [{ type: 'text', text: `Godot editor launched successfully for project at ${args.projectPath}.\nNote: the editor is a GUI application and cannot be controlled programmatically. Use
|
|
470
|
+
content: [{ type: 'text', 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.` }],
|
|
445
471
|
};
|
|
446
472
|
}
|
|
447
473
|
catch (error) {
|
|
@@ -910,136 +936,194 @@ function parseProjectSettings(projectFilePath) {
|
|
|
910
936
|
}
|
|
911
937
|
return result;
|
|
912
938
|
}
|
|
913
|
-
export async function
|
|
939
|
+
export async function handleListAutoloads(args) {
|
|
914
940
|
args = normalizeParameters(args);
|
|
915
|
-
const
|
|
916
|
-
if (
|
|
917
|
-
return
|
|
941
|
+
const v = validateProjectArgs(args);
|
|
942
|
+
if ('isError' in v)
|
|
943
|
+
return v;
|
|
944
|
+
try {
|
|
945
|
+
const projectFile = join(v.projectPath, 'project.godot');
|
|
946
|
+
const autoloads = parseAutoloads(projectFile);
|
|
947
|
+
return { content: [{ type: 'text', text: JSON.stringify({ autoloads }) }] };
|
|
918
948
|
}
|
|
919
|
-
|
|
920
|
-
|
|
949
|
+
catch (error) {
|
|
950
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
951
|
+
return createErrorResponse(`Failed to list autoloads: ${errorMessage}`, ['Check if project.godot is accessible']);
|
|
921
952
|
}
|
|
922
|
-
|
|
923
|
-
|
|
953
|
+
}
|
|
954
|
+
export async function handleAddAutoload(args) {
|
|
955
|
+
args = normalizeParameters(args);
|
|
956
|
+
const v = validateProjectArgs(args);
|
|
957
|
+
if ('isError' in v)
|
|
958
|
+
return v;
|
|
959
|
+
if (!args.autoloadName || !args.autoloadPath) {
|
|
960
|
+
return createErrorResponse('autoloadName and autoloadPath are required', ['Provide the autoload node name and script/scene path']);
|
|
924
961
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
return createErrorResponse(`Not a valid Godot project: ${args.projectPath}`, ['Ensure the path points to a directory containing a project.godot file']);
|
|
962
|
+
if (!validatePath(args.autoloadPath)) {
|
|
963
|
+
return createErrorResponse('Invalid autoload path', ['Provide a valid path without ".."']);
|
|
928
964
|
}
|
|
929
965
|
try {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
966
|
+
const projectFile = join(v.projectPath, 'project.godot');
|
|
967
|
+
const existing = parseAutoloads(projectFile);
|
|
968
|
+
if (existing.some(a => a.name === args.autoloadName)) {
|
|
969
|
+
return createErrorResponse(`Autoload '${args.autoloadName}' already exists`, ['Use update_autoload to modify it', 'Use list_autoloads to see current autoloads']);
|
|
970
|
+
}
|
|
971
|
+
const isSingleton = args.singleton !== false;
|
|
972
|
+
addAutoloadEntry(projectFile, args.autoloadName, args.autoloadPath, isSingleton);
|
|
973
|
+
return {
|
|
974
|
+
content: [{ type: 'text', 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.` }],
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
catch (error) {
|
|
978
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
979
|
+
return createErrorResponse(`Failed to add autoload: ${errorMessage}`, ['Check if project.godot is accessible']);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
export async function handleRemoveAutoload(args) {
|
|
983
|
+
args = normalizeParameters(args);
|
|
984
|
+
const v = validateProjectArgs(args);
|
|
985
|
+
if ('isError' in v)
|
|
986
|
+
return v;
|
|
987
|
+
if (!args.autoloadName) {
|
|
988
|
+
return createErrorResponse('autoloadName is required', ['Provide the name of the autoload to remove']);
|
|
989
|
+
}
|
|
990
|
+
try {
|
|
991
|
+
const projectFile = join(v.projectPath, 'project.godot');
|
|
992
|
+
const removed = removeAutoloadEntry(projectFile, args.autoloadName);
|
|
993
|
+
if (!removed) {
|
|
994
|
+
return createErrorResponse(`Autoload '${args.autoloadName}' not found`, ['Use list_autoloads to see existing autoloads']);
|
|
995
|
+
}
|
|
996
|
+
return { content: [{ type: 'text', text: `Autoload '${args.autoloadName}' removed successfully.` }] };
|
|
997
|
+
}
|
|
998
|
+
catch (error) {
|
|
999
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1000
|
+
return createErrorResponse(`Failed to remove autoload: ${errorMessage}`, ['Check if project.godot is accessible']);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
export async function handleUpdateAutoload(args) {
|
|
1004
|
+
args = normalizeParameters(args);
|
|
1005
|
+
const v = validateProjectArgs(args);
|
|
1006
|
+
if ('isError' in v)
|
|
1007
|
+
return v;
|
|
1008
|
+
if (!args.autoloadName) {
|
|
1009
|
+
return createErrorResponse('autoloadName is required', ['Provide the name of the autoload to update']);
|
|
1010
|
+
}
|
|
1011
|
+
if (args.autoloadPath && !validatePath(args.autoloadPath)) {
|
|
1012
|
+
return createErrorResponse('Invalid autoload path', ['Provide a valid path without ".."']);
|
|
1013
|
+
}
|
|
1014
|
+
try {
|
|
1015
|
+
const projectFile = join(v.projectPath, 'project.godot');
|
|
1016
|
+
const updated = updateAutoloadEntry(projectFile, args.autoloadName, args.autoloadPath, args.singleton);
|
|
1017
|
+
if (!updated) {
|
|
1018
|
+
return createErrorResponse(`Autoload '${args.autoloadName}' not found`, ['Use list_autoloads to see existing autoloads', 'Use add_autoload to register a new one']);
|
|
1019
|
+
}
|
|
1020
|
+
return { content: [{ type: 'text', text: `Autoload '${args.autoloadName}' updated successfully.` }] };
|
|
1021
|
+
}
|
|
1022
|
+
catch (error) {
|
|
1023
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1024
|
+
return createErrorResponse(`Failed to update autoload: ${errorMessage}`, ['Check if project.godot is accessible']);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
export async function handleGetProjectFiles(args) {
|
|
1028
|
+
args = normalizeParameters(args);
|
|
1029
|
+
const v = validateProjectArgs(args);
|
|
1030
|
+
if ('isError' in v)
|
|
1031
|
+
return v;
|
|
1032
|
+
try {
|
|
1033
|
+
const maxDepth = typeof args.maxDepth === 'number' ? args.maxDepth : -1;
|
|
1034
|
+
const extensions = Array.isArray(args.extensions)
|
|
1035
|
+
? args.extensions.map(e => e.toLowerCase().replace(/^\./, ''))
|
|
1036
|
+
: null;
|
|
1037
|
+
const tree = buildFilesystemTree(v.projectPath, '', maxDepth, 0, extensions);
|
|
1038
|
+
return { content: [{ type: 'text', text: JSON.stringify(tree) }] };
|
|
1039
|
+
}
|
|
1040
|
+
catch (error) {
|
|
1041
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1042
|
+
return createErrorResponse(`Failed to get project files: ${errorMessage}`, ['Check if the project directory is accessible']);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
export async function handleSearchProject(args) {
|
|
1046
|
+
args = normalizeParameters(args);
|
|
1047
|
+
const v = validateProjectArgs(args);
|
|
1048
|
+
if ('isError' in v)
|
|
1049
|
+
return v;
|
|
1050
|
+
if (!args.pattern || typeof args.pattern !== 'string') {
|
|
1051
|
+
return createErrorResponse('pattern is required', ['Provide a plain-text search string']);
|
|
1052
|
+
}
|
|
1053
|
+
try {
|
|
1054
|
+
const fileTypes = Array.isArray(args.fileTypes)
|
|
1055
|
+
? args.fileTypes.map(e => e.toLowerCase().replace(/^\./, ''))
|
|
1056
|
+
: ['gd', 'tscn', 'cs', 'gdshader'];
|
|
1057
|
+
const caseSensitive = args.caseSensitive === true;
|
|
1058
|
+
const maxResults = typeof args.maxResults === 'number' ? args.maxResults : 100;
|
|
1059
|
+
const result = searchInFiles(v.projectPath, args.pattern, fileTypes, caseSensitive, maxResults);
|
|
1060
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
1061
|
+
}
|
|
1062
|
+
catch (error) {
|
|
1063
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1064
|
+
return createErrorResponse(`Failed to search project: ${errorMessage}`, ['Check if the project directory is accessible']);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
export async function handleGetSceneDependencies(args) {
|
|
1068
|
+
args = normalizeParameters(args);
|
|
1069
|
+
const v = validateProjectArgs(args);
|
|
1070
|
+
if ('isError' in v)
|
|
1071
|
+
return v;
|
|
1072
|
+
if (!args.scenePath || typeof args.scenePath !== 'string') {
|
|
1073
|
+
return createErrorResponse('scenePath is required', ['Provide a path relative to the project root, e.g. "scenes/main.tscn"']);
|
|
1074
|
+
}
|
|
1075
|
+
if (!validatePath(args.scenePath)) {
|
|
1076
|
+
return createErrorResponse('Invalid scenePath', ['Provide a valid path without ".."']);
|
|
1077
|
+
}
|
|
1078
|
+
try {
|
|
1079
|
+
const sceneFullPath = join(v.projectPath, args.scenePath);
|
|
1080
|
+
if (!existsSync(sceneFullPath)) {
|
|
1081
|
+
return createErrorResponse(`Scene file not found: ${args.scenePath}`, ['Verify the path is relative to the project root', 'Use get_project_files to list available .tscn files']);
|
|
1082
|
+
}
|
|
1083
|
+
const sceneContent = readFileSync(sceneFullPath, 'utf8');
|
|
1084
|
+
const dependencies = [];
|
|
1085
|
+
const extResourcePattern = /^\[ext_resource([^\]]*)\]/gm;
|
|
1086
|
+
let match;
|
|
1087
|
+
while ((match = extResourcePattern.exec(sceneContent)) !== null) {
|
|
1088
|
+
const attrs = match[1];
|
|
1089
|
+
const typeMatch = attrs.match(/\btype="([^"]*)"/);
|
|
1090
|
+
const pathMatch = attrs.match(/\bpath="([^"]*)"/);
|
|
1091
|
+
const uidMatch = attrs.match(/\buid="([^"]*)"/);
|
|
1092
|
+
if (pathMatch) {
|
|
1093
|
+
const depPath = pathMatch[1].replace(/^res:\/\//, '');
|
|
1094
|
+
const dep = {
|
|
1095
|
+
path: depPath,
|
|
1096
|
+
type: typeMatch ? typeMatch[1] : 'Unknown',
|
|
950
1097
|
};
|
|
1098
|
+
if (uidMatch)
|
|
1099
|
+
dep.uid = uidMatch[1];
|
|
1100
|
+
dependencies.push(dep);
|
|
951
1101
|
}
|
|
952
|
-
case 'remove_autoload': {
|
|
953
|
-
if (!args.autoloadName) {
|
|
954
|
-
return createErrorResponse('autoloadName is required for remove_autoload', ['Provide the name of the autoload to remove']);
|
|
955
|
-
}
|
|
956
|
-
const removed = removeAutoloadEntry(projectFile, args.autoloadName);
|
|
957
|
-
if (!removed) {
|
|
958
|
-
return createErrorResponse(`Autoload '${args.autoloadName}' not found`, ['Use list_autoloads to see existing autoloads']);
|
|
959
|
-
}
|
|
960
|
-
return { content: [{ type: 'text', text: `Autoload '${args.autoloadName}' removed successfully.` }] };
|
|
961
|
-
}
|
|
962
|
-
case 'update_autoload': {
|
|
963
|
-
if (!args.autoloadName) {
|
|
964
|
-
return createErrorResponse('autoloadName is required for update_autoload', ['Provide the name of the autoload to update']);
|
|
965
|
-
}
|
|
966
|
-
if (args.autoloadPath && !validatePath(args.autoloadPath)) {
|
|
967
|
-
return createErrorResponse('Invalid autoload path', ['Provide a valid path without ".."']);
|
|
968
|
-
}
|
|
969
|
-
const updated = updateAutoloadEntry(projectFile, args.autoloadName, args.autoloadPath, args.singleton);
|
|
970
|
-
if (!updated) {
|
|
971
|
-
return createErrorResponse(`Autoload '${args.autoloadName}' not found`, ['Use list_autoloads to see existing autoloads', 'Use add_autoload to register a new autoload']);
|
|
972
|
-
}
|
|
973
|
-
return { content: [{ type: 'text', text: `Autoload '${args.autoloadName}' updated successfully.` }] };
|
|
974
|
-
}
|
|
975
|
-
case 'get_filesystem_tree': {
|
|
976
|
-
const maxDepth = typeof args.maxDepth === 'number' ? args.maxDepth : -1;
|
|
977
|
-
const extensions = Array.isArray(args.extensions)
|
|
978
|
-
? args.extensions.map(e => e.toLowerCase().replace(/^\./, ''))
|
|
979
|
-
: null;
|
|
980
|
-
const tree = buildFilesystemTree(args.projectPath, '', maxDepth, 0, extensions);
|
|
981
|
-
return { content: [{ type: 'text', text: JSON.stringify(tree) }] };
|
|
982
|
-
}
|
|
983
|
-
case 'search_in_files': {
|
|
984
|
-
if (!args.pattern || typeof args.pattern !== 'string') {
|
|
985
|
-
return createErrorResponse('pattern is required for search_in_files', ['Provide a plain-text search string']);
|
|
986
|
-
}
|
|
987
|
-
const fileTypes = Array.isArray(args.fileTypes)
|
|
988
|
-
? args.fileTypes.map(e => e.toLowerCase().replace(/^\./, ''))
|
|
989
|
-
: ['gd', 'tscn', 'cs', 'gdshader'];
|
|
990
|
-
const caseSensitive = args.caseSensitive === true;
|
|
991
|
-
const maxResults = typeof args.maxResults === 'number' ? args.maxResults : 100;
|
|
992
|
-
const result = searchInFiles(args.projectPath, args.pattern, fileTypes, caseSensitive, maxResults);
|
|
993
|
-
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
994
|
-
}
|
|
995
|
-
case 'get_scene_dependencies': {
|
|
996
|
-
if (!args.scenePath || typeof args.scenePath !== 'string') {
|
|
997
|
-
return createErrorResponse('scenePath is required for get_scene_dependencies', ['Provide a path relative to the project root, e.g. "scenes/main.tscn"']);
|
|
998
|
-
}
|
|
999
|
-
if (!validatePath(args.scenePath)) {
|
|
1000
|
-
return createErrorResponse('Invalid scenePath', ['Provide a valid path without ".."']);
|
|
1001
|
-
}
|
|
1002
|
-
const sceneFullPath = join(args.projectPath, args.scenePath);
|
|
1003
|
-
if (!existsSync(sceneFullPath)) {
|
|
1004
|
-
return createErrorResponse(`Scene file not found: ${args.scenePath}`, ['Verify the path is relative to the project root', 'Use get_filesystem_tree to list available .tscn files']);
|
|
1005
|
-
}
|
|
1006
|
-
const sceneContent = readFileSync(sceneFullPath, 'utf8');
|
|
1007
|
-
const dependencies = [];
|
|
1008
|
-
const extResourcePattern = /^\[ext_resource([^\]]*)\]/gm;
|
|
1009
|
-
let match;
|
|
1010
|
-
while ((match = extResourcePattern.exec(sceneContent)) !== null) {
|
|
1011
|
-
const attrs = match[1];
|
|
1012
|
-
const typeMatch = attrs.match(/\btype="([^"]*)"/);
|
|
1013
|
-
const pathMatch = attrs.match(/\bpath="([^"]*)"/);
|
|
1014
|
-
const uidMatch = attrs.match(/\buid="([^"]*)"/);
|
|
1015
|
-
if (pathMatch) {
|
|
1016
|
-
const depPath = pathMatch[1].replace(/^res:\/\//, '');
|
|
1017
|
-
const dep = {
|
|
1018
|
-
path: depPath,
|
|
1019
|
-
type: typeMatch ? typeMatch[1] : 'Unknown',
|
|
1020
|
-
};
|
|
1021
|
-
if (uidMatch)
|
|
1022
|
-
dep.uid = uidMatch[1];
|
|
1023
|
-
dependencies.push(dep);
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
return { content: [{ type: 'text', text: JSON.stringify({ scene: args.scenePath, dependencies }) }] };
|
|
1027
|
-
}
|
|
1028
|
-
case 'get_project_settings': {
|
|
1029
|
-
const allSettings = parseProjectSettings(projectFile);
|
|
1030
|
-
if (args.section && typeof args.section === 'string') {
|
|
1031
|
-
const sectionData = allSettings[args.section] ?? {};
|
|
1032
|
-
return { content: [{ type: 'text', text: JSON.stringify({ settings: sectionData }) }] };
|
|
1033
|
-
}
|
|
1034
|
-
return { content: [{ type: 'text', text: JSON.stringify({ settings: allSettings }) }] };
|
|
1035
|
-
}
|
|
1036
|
-
default:
|
|
1037
|
-
return createErrorResponse(`Unknown operation: ${operation}`, ['Use one of: list_autoloads, add_autoload, remove_autoload, update_autoload, get_filesystem_tree, search_in_files, get_scene_dependencies, get_project_settings']);
|
|
1038
1102
|
}
|
|
1103
|
+
return { content: [{ type: 'text', text: JSON.stringify({ scene: args.scenePath, dependencies }) }] };
|
|
1104
|
+
}
|
|
1105
|
+
catch (error) {
|
|
1106
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1107
|
+
return createErrorResponse(`Failed to get scene dependencies: ${errorMessage}`, ['Check if the scene file is accessible']);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
export async function handleGetProjectSettings(args) {
|
|
1111
|
+
args = normalizeParameters(args);
|
|
1112
|
+
const v = validateProjectArgs(args);
|
|
1113
|
+
if ('isError' in v)
|
|
1114
|
+
return v;
|
|
1115
|
+
try {
|
|
1116
|
+
const projectFile = join(v.projectPath, 'project.godot');
|
|
1117
|
+
const allSettings = parseProjectSettings(projectFile);
|
|
1118
|
+
if (args.section && typeof args.section === 'string') {
|
|
1119
|
+
const sectionData = allSettings[args.section] ?? {};
|
|
1120
|
+
return { content: [{ type: 'text', text: JSON.stringify({ settings: sectionData }) }] };
|
|
1121
|
+
}
|
|
1122
|
+
return { content: [{ type: 'text', text: JSON.stringify({ settings: allSettings }) }] };
|
|
1039
1123
|
}
|
|
1040
1124
|
catch (error) {
|
|
1041
1125
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1042
|
-
return createErrorResponse(`Failed to
|
|
1126
|
+
return createErrorResponse(`Failed to get project settings: ${errorMessage}`, ['Check if project.godot is accessible']);
|
|
1043
1127
|
}
|
|
1044
1128
|
}
|
|
1045
1129
|
//# sourceMappingURL=project-tools.js.map
|