@n24q02m/better-godot-mcp 1.2.1 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/cli.mjs +103 -113
- package/build/src/tools/composite/animation.d.ts.map +1 -1
- package/build/src/tools/composite/animation.js +2 -1
- package/build/src/tools/composite/animation.js.map +1 -1
- package/build/src/tools/composite/audio.d.ts.map +1 -1
- package/build/src/tools/composite/audio.js +2 -1
- package/build/src/tools/composite/audio.js.map +1 -1
- package/build/src/tools/composite/config.d.ts.map +1 -1
- package/build/src/tools/composite/config.js +4 -0
- package/build/src/tools/composite/config.js.map +1 -1
- package/build/src/tools/composite/editor.js +4 -4
- package/build/src/tools/composite/editor.js.map +1 -1
- package/build/src/tools/composite/navigation.d.ts.map +1 -1
- package/build/src/tools/composite/navigation.js +2 -1
- package/build/src/tools/composite/navigation.js.map +1 -1
- package/build/src/tools/composite/nodes.d.ts.map +1 -1
- package/build/src/tools/composite/nodes.js +2 -1
- package/build/src/tools/composite/nodes.js.map +1 -1
- package/build/src/tools/composite/physics.d.ts.map +1 -1
- package/build/src/tools/composite/physics.js +3 -2
- package/build/src/tools/composite/physics.js.map +1 -1
- package/build/src/tools/composite/project.d.ts.map +1 -1
- package/build/src/tools/composite/project.js +2 -1
- package/build/src/tools/composite/project.js.map +1 -1
- package/build/src/tools/composite/resources.d.ts.map +1 -1
- package/build/src/tools/composite/resources.js +12 -14
- package/build/src/tools/composite/resources.js.map +1 -1
- package/build/src/tools/composite/scenes.d.ts.map +1 -1
- package/build/src/tools/composite/scenes.js +13 -13
- package/build/src/tools/composite/scenes.js.map +1 -1
- package/build/src/tools/composite/scripts.d.ts.map +1 -1
- package/build/src/tools/composite/scripts.js +10 -9
- package/build/src/tools/composite/scripts.js.map +1 -1
- package/build/src/tools/composite/shader.d.ts.map +1 -1
- package/build/src/tools/composite/shader.js +5 -4
- package/build/src/tools/composite/shader.js.map +1 -1
- package/build/src/tools/composite/signals.d.ts.map +1 -1
- package/build/src/tools/composite/signals.js +2 -1
- package/build/src/tools/composite/signals.js.map +1 -1
- package/build/src/tools/composite/tilemap.d.ts.map +1 -1
- package/build/src/tools/composite/tilemap.js +4 -3
- package/build/src/tools/composite/tilemap.js.map +1 -1
- package/build/src/tools/composite/ui.d.ts.map +1 -1
- package/build/src/tools/composite/ui.js +3 -2
- package/build/src/tools/composite/ui.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
**Composite MCP Server for Godot Engine - Optimized for AI Agents**
|
|
4
4
|
|
|
5
5
|
[](https://github.com/n24q02m/better-godot-mcp/actions/workflows/ci.yml)
|
|
6
|
-
[](https://codecov.io/gh/n24q02m/better-godot-mcp)
|
|
7
7
|
[](https://www.npmjs.com/package/@n24q02m/better-godot-mcp)
|
|
8
8
|
[](https://hub.docker.com/r/n24q02m/better-godot-mcp)
|
|
9
9
|
[](LICENSE)
|
package/bin/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// @n24q02m/better-godot-mcp v1.2.
|
|
2
|
+
// @n24q02m/better-godot-mcp v1.2.3
|
|
3
3
|
// Auto-generated CLI bundle - do not edit directly
|
|
4
4
|
|
|
5
5
|
|
|
@@ -161,7 +161,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprot
|
|
|
161
161
|
|
|
162
162
|
// src/tools/composite/animation.ts
|
|
163
163
|
import { existsSync as existsSync2, readFileSync, writeFileSync } from "node:fs";
|
|
164
|
-
import { resolve } from "node:path";
|
|
164
|
+
import { resolve as resolve2 } from "node:path";
|
|
165
165
|
|
|
166
166
|
// src/tools/helpers/errors.ts
|
|
167
167
|
var GodotMCPError = class extends Error {
|
|
@@ -198,9 +198,25 @@ function formatJSON(data) {
|
|
|
198
198
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
// src/tools/helpers/paths.ts
|
|
202
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
203
|
+
function safeResolve(baseDir, targetPath) {
|
|
204
|
+
const resolvedBase = resolve(baseDir);
|
|
205
|
+
const resolvedTarget = resolve(resolvedBase, targetPath);
|
|
206
|
+
const relativePath = relative(resolvedBase, resolvedTarget);
|
|
207
|
+
if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
208
|
+
throw new GodotMCPError(
|
|
209
|
+
`Access denied: Path '${targetPath}' resolves to '${resolvedTarget}' which is outside the project root '${resolvedBase}'.`,
|
|
210
|
+
"INVALID_ARGS",
|
|
211
|
+
"Ensure all file paths are within the project directory."
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
return resolvedTarget;
|
|
215
|
+
}
|
|
216
|
+
|
|
201
217
|
// src/tools/composite/animation.ts
|
|
202
218
|
function resolveScene(projectPath, scenePath) {
|
|
203
|
-
const fullPath = projectPath ?
|
|
219
|
+
const fullPath = projectPath ? safeResolve(projectPath, scenePath) : resolve2(scenePath);
|
|
204
220
|
if (!existsSync2(fullPath))
|
|
205
221
|
throw new GodotMCPError(`Scene not found: ${scenePath}`, "SCENE_ERROR", "Check the file path.");
|
|
206
222
|
return fullPath;
|
|
@@ -326,13 +342,13 @@ Track data format: tracks/N/keys = { "times": PackedFloat32Array(0, 1), "values"
|
|
|
326
342
|
|
|
327
343
|
// src/tools/composite/audio.ts
|
|
328
344
|
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
329
|
-
import { resolve as
|
|
345
|
+
import { resolve as resolve3 } from "node:path";
|
|
330
346
|
async function handleAudio(action, args, config) {
|
|
331
347
|
const projectPath = args.project_path || config.projectPath;
|
|
332
348
|
switch (action) {
|
|
333
349
|
case "list_buses": {
|
|
334
350
|
if (!projectPath) throw new GodotMCPError("No project path specified", "INVALID_ARGS", "Provide project_path.");
|
|
335
|
-
const busLayoutPath =
|
|
351
|
+
const busLayoutPath = resolve3(projectPath, "default_bus_layout.tres");
|
|
336
352
|
if (!existsSync3(busLayoutPath)) {
|
|
337
353
|
return formatJSON({ buses: [{ name: "Master", volume: 0, effects: [] }], note: "Using default bus layout." });
|
|
338
354
|
}
|
|
@@ -350,7 +366,7 @@ async function handleAudio(action, args, config) {
|
|
|
350
366
|
const busName = args.bus_name;
|
|
351
367
|
if (!busName) throw new GodotMCPError("No bus_name specified", "INVALID_ARGS", "Provide bus name.");
|
|
352
368
|
const sendTo = args.send_to || "Master";
|
|
353
|
-
const busLayoutPath =
|
|
369
|
+
const busLayoutPath = resolve3(projectPath, "default_bus_layout.tres");
|
|
354
370
|
let content;
|
|
355
371
|
if (existsSync3(busLayoutPath)) {
|
|
356
372
|
content = readFileSync2(busLayoutPath, "utf-8");
|
|
@@ -394,7 +410,7 @@ ${newBus}
|
|
|
394
410
|
);
|
|
395
411
|
}
|
|
396
412
|
const fullEffectType = effectType.startsWith("AudioEffect") ? effectType : `AudioEffect${effectType}`;
|
|
397
|
-
const busLayoutPath =
|
|
413
|
+
const busLayoutPath = resolve3(projectPath, "default_bus_layout.tres");
|
|
398
414
|
let content;
|
|
399
415
|
if (existsSync3(busLayoutPath)) {
|
|
400
416
|
content = readFileSync2(busLayoutPath, "utf-8");
|
|
@@ -451,7 +467,7 @@ ${effectRef}`;
|
|
|
451
467
|
const streamType = args.stream_type || "2D";
|
|
452
468
|
const parent = args.parent || ".";
|
|
453
469
|
const bus = args.bus || "Master";
|
|
454
|
-
const fullPath = projectPath ?
|
|
470
|
+
const fullPath = projectPath ? safeResolve(projectPath, scenePath) : resolve3(scenePath);
|
|
455
471
|
if (!existsSync3(fullPath))
|
|
456
472
|
throw new GodotMCPError(`Scene not found: ${scenePath}`, "SCENE_ERROR", "Check file path.");
|
|
457
473
|
let content = readFileSync2(fullPath, "utf-8");
|
|
@@ -500,6 +516,13 @@ async function handleConfig(action, args, config) {
|
|
|
500
516
|
if (!validKeys.includes(key)) {
|
|
501
517
|
throw new GodotMCPError(`Invalid config key: ${key}`, "INVALID_ARGS", `Valid keys: ${validKeys.join(", ")}`);
|
|
502
518
|
}
|
|
519
|
+
if ((key === "project_path" || key === "godot_path") && /[;&|`$(){}]/.test(value)) {
|
|
520
|
+
throw new GodotMCPError(
|
|
521
|
+
`Invalid characters in ${key}`,
|
|
522
|
+
"INVALID_ARGS",
|
|
523
|
+
"Path must not contain shell metacharacters: ; & | ` $ ( ) { }"
|
|
524
|
+
);
|
|
525
|
+
}
|
|
503
526
|
runtimeConfig[key] = value;
|
|
504
527
|
if (key === "project_path") {
|
|
505
528
|
config.projectPath = value;
|
|
@@ -518,8 +541,8 @@ async function handleConfig(action, args, config) {
|
|
|
518
541
|
}
|
|
519
542
|
|
|
520
543
|
// src/tools/composite/editor.ts
|
|
521
|
-
import {
|
|
522
|
-
import { resolve as
|
|
544
|
+
import { execFile } from "node:child_process";
|
|
545
|
+
import { resolve as resolve4 } from "node:path";
|
|
523
546
|
import { promisify } from "node:util";
|
|
524
547
|
|
|
525
548
|
// src/godot/headless.ts
|
|
@@ -568,11 +591,11 @@ function launchGodotEditor(godotPath, projectPath) {
|
|
|
568
591
|
}
|
|
569
592
|
|
|
570
593
|
// src/tools/composite/editor.ts
|
|
571
|
-
var
|
|
594
|
+
var execFileAsync = promisify(execFile);
|
|
572
595
|
async function getGodotProcessesAsync() {
|
|
573
596
|
try {
|
|
574
597
|
if (process.platform === "win32") {
|
|
575
|
-
const { stdout: stdout2 } = await
|
|
598
|
+
const { stdout: stdout2 } = await execFileAsync("tasklist", ["/FI", "IMAGENAME eq godot*", "/FO", "CSV", "/NH"], {
|
|
576
599
|
encoding: "utf-8"
|
|
577
600
|
});
|
|
578
601
|
return stdout2.split("\n").filter((line) => line.includes("godot")).map((line) => {
|
|
@@ -580,7 +603,7 @@ async function getGodotProcessesAsync() {
|
|
|
580
603
|
return { pid: parts[1] || "unknown", name: parts[0] || "godot" };
|
|
581
604
|
});
|
|
582
605
|
}
|
|
583
|
-
const { stdout } = await
|
|
606
|
+
const { stdout } = await execFileAsync("pgrep", ["-la", "godot"], {
|
|
584
607
|
encoding: "utf-8"
|
|
585
608
|
});
|
|
586
609
|
return stdout.split("\n").filter(Boolean).map((line) => {
|
|
@@ -605,7 +628,7 @@ async function handleEditor(action, args, config) {
|
|
|
605
628
|
if (!projectPath) {
|
|
606
629
|
throw new GodotMCPError("No project path specified", "INVALID_ARGS", "Provide project_path argument.");
|
|
607
630
|
}
|
|
608
|
-
const { pid } = launchGodotEditor(config.godotPath,
|
|
631
|
+
const { pid } = launchGodotEditor(config.godotPath, resolve4(projectPath));
|
|
609
632
|
return formatSuccess(`Godot editor launched (PID: ${pid})`);
|
|
610
633
|
}
|
|
611
634
|
case "status": {
|
|
@@ -680,7 +703,7 @@ async function handleHelp(action, args) {
|
|
|
680
703
|
|
|
681
704
|
// src/tools/composite/input-map.ts
|
|
682
705
|
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
683
|
-
import { join as join3, resolve as
|
|
706
|
+
import { join as join3, resolve as resolve5 } from "node:path";
|
|
684
707
|
var GODOT_KEY_CODES = {
|
|
685
708
|
// Letters (ASCII)
|
|
686
709
|
KEY_A: 65,
|
|
@@ -790,7 +813,7 @@ function resolveMouseCode(value) {
|
|
|
790
813
|
}
|
|
791
814
|
function getProjectGodotPath(projectPath) {
|
|
792
815
|
if (!projectPath) throw new GodotMCPError("No project path specified", "INVALID_ARGS", "Provide project_path.");
|
|
793
|
-
const configPath = join3(
|
|
816
|
+
const configPath = join3(resolve5(projectPath), "project.godot");
|
|
794
817
|
if (!existsSync5(configPath))
|
|
795
818
|
throw new GodotMCPError("No project.godot found", "PROJECT_NOT_FOUND", "Verify the project path.");
|
|
796
819
|
return configPath;
|
|
@@ -970,9 +993,9 @@ ${actionLine}`);
|
|
|
970
993
|
|
|
971
994
|
// src/tools/composite/navigation.ts
|
|
972
995
|
import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
973
|
-
import { resolve as
|
|
996
|
+
import { resolve as resolve6 } from "node:path";
|
|
974
997
|
function resolveScene2(projectPath, scenePath) {
|
|
975
|
-
const fullPath = projectPath ?
|
|
998
|
+
const fullPath = projectPath ? safeResolve(projectPath, scenePath) : resolve6(scenePath);
|
|
976
999
|
if (!existsSync6(fullPath))
|
|
977
1000
|
throw new GodotMCPError(`Scene not found: ${scenePath}`, "SCENE_ERROR", "Check the file path.");
|
|
978
1001
|
return fullPath;
|
|
@@ -1054,7 +1077,7 @@ async function handleNavigation(action, args, config) {
|
|
|
1054
1077
|
|
|
1055
1078
|
// src/tools/composite/nodes.ts
|
|
1056
1079
|
import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
1057
|
-
import { resolve as
|
|
1080
|
+
import { resolve as resolve7 } from "node:path";
|
|
1058
1081
|
|
|
1059
1082
|
// src/tools/helpers/scene-parser.ts
|
|
1060
1083
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
@@ -1293,7 +1316,7 @@ function mapToSceneNode(node) {
|
|
|
1293
1316
|
};
|
|
1294
1317
|
}
|
|
1295
1318
|
function resolveScenePath(projectPath, scenePath) {
|
|
1296
|
-
return projectPath ?
|
|
1319
|
+
return projectPath ? safeResolve(projectPath, scenePath) : resolve7(scenePath);
|
|
1297
1320
|
}
|
|
1298
1321
|
async function handleNodes(action, args, config) {
|
|
1299
1322
|
const projectPath = args.project_path || config.projectPath;
|
|
@@ -1424,7 +1447,7 @@ ${nodeDecl}`;
|
|
|
1424
1447
|
|
|
1425
1448
|
// src/tools/composite/physics.ts
|
|
1426
1449
|
import { existsSync as existsSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "node:fs";
|
|
1427
|
-
import { join as join4, resolve as
|
|
1450
|
+
import { join as join4, resolve as resolve8 } from "node:path";
|
|
1428
1451
|
|
|
1429
1452
|
// src/tools/helpers/project-settings.ts
|
|
1430
1453
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "node:fs";
|
|
@@ -1531,7 +1554,7 @@ async function handlePhysics(action, args, config) {
|
|
|
1531
1554
|
switch (action) {
|
|
1532
1555
|
case "layers": {
|
|
1533
1556
|
if (!projectPath) throw new GodotMCPError("No project path specified", "INVALID_ARGS", "Provide project_path.");
|
|
1534
|
-
const configPath = join4(
|
|
1557
|
+
const configPath = join4(resolve8(projectPath), "project.godot");
|
|
1535
1558
|
if (!existsSync8(configPath))
|
|
1536
1559
|
throw new GodotMCPError("No project.godot found", "PROJECT_NOT_FOUND", "Verify project path.");
|
|
1537
1560
|
const settings = parseProjectSettings(configPath);
|
|
@@ -1553,7 +1576,7 @@ async function handlePhysics(action, args, config) {
|
|
|
1553
1576
|
if (!nodeName) throw new GodotMCPError("No node name specified", "INVALID_ARGS", "Provide node name.");
|
|
1554
1577
|
const collisionLayer = args.collision_layer;
|
|
1555
1578
|
const collisionMask = args.collision_mask;
|
|
1556
|
-
const fullPath = projectPath ?
|
|
1579
|
+
const fullPath = projectPath ? safeResolve(projectPath, scenePath) : resolve8(scenePath);
|
|
1557
1580
|
if (!existsSync8(fullPath))
|
|
1558
1581
|
throw new GodotMCPError(`Scene not found: ${scenePath}`, "SCENE_ERROR", "Check file path.");
|
|
1559
1582
|
let content = readFileSync9(fullPath, "utf-8");
|
|
@@ -1579,7 +1602,7 @@ collision_mask = ${collisionMask}`;
|
|
|
1579
1602
|
if (!scenePath) throw new GodotMCPError("No scene_path specified", "INVALID_ARGS", "Provide scene_path.");
|
|
1580
1603
|
const nodeName = args.name;
|
|
1581
1604
|
if (!nodeName) throw new GodotMCPError("No node name specified", "INVALID_ARGS", "Provide node name.");
|
|
1582
|
-
const fullPath = projectPath ?
|
|
1605
|
+
const fullPath = projectPath ? safeResolve(projectPath, scenePath) : resolve8(scenePath);
|
|
1583
1606
|
if (!existsSync8(fullPath))
|
|
1584
1607
|
throw new GodotMCPError(`Scene not found: ${scenePath}`, "SCENE_ERROR", "Check file path.");
|
|
1585
1608
|
let content = readFileSync9(fullPath, "utf-8");
|
|
@@ -1610,7 +1633,7 @@ freeze = ${args.freeze}`;
|
|
|
1610
1633
|
const dimension = args.dimension || "2d";
|
|
1611
1634
|
const name = args.name;
|
|
1612
1635
|
if (!name) throw new GodotMCPError("No name specified", "INVALID_ARGS", "Provide layer name.");
|
|
1613
|
-
const configPath = join4(
|
|
1636
|
+
const configPath = join4(resolve8(projectPath), "project.godot");
|
|
1614
1637
|
if (!existsSync8(configPath))
|
|
1615
1638
|
throw new GodotMCPError("No project.godot found", "PROJECT_NOT_FOUND", "Verify project path.");
|
|
1616
1639
|
const content = readFileSync9(configPath, "utf-8");
|
|
@@ -1632,7 +1655,7 @@ freeze = ${args.freeze}`;
|
|
|
1632
1655
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1633
1656
|
import { existsSync as existsSync9 } from "node:fs";
|
|
1634
1657
|
import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1635
|
-
import { join as join5, resolve as
|
|
1658
|
+
import { join as join5, resolve as resolve9 } from "node:path";
|
|
1636
1659
|
async function parseProjectGodot(projectPath) {
|
|
1637
1660
|
const configPath = join5(projectPath, "project.godot");
|
|
1638
1661
|
if (!existsSync9(configPath)) {
|
|
@@ -1683,7 +1706,7 @@ async function handleProject(action, args, config) {
|
|
|
1683
1706
|
"Provide project_path argument or set it via config.set action."
|
|
1684
1707
|
);
|
|
1685
1708
|
}
|
|
1686
|
-
const info = await parseProjectGodot(
|
|
1709
|
+
const info = await parseProjectGodot(resolve9(projectPath));
|
|
1687
1710
|
return formatJSON(info);
|
|
1688
1711
|
}
|
|
1689
1712
|
case "version": {
|
|
@@ -1699,7 +1722,7 @@ async function handleProject(action, args, config) {
|
|
|
1699
1722
|
const projectPath = args.project_path || config.projectPath;
|
|
1700
1723
|
if (!projectPath)
|
|
1701
1724
|
throw new GodotMCPError("No project path specified", "INVALID_ARGS", "Provide project_path argument.");
|
|
1702
|
-
const { pid } = runGodotProject(config.godotPath,
|
|
1725
|
+
const { pid } = runGodotProject(config.godotPath, resolve9(projectPath));
|
|
1703
1726
|
return formatSuccess(`Godot project started (PID: ${pid})`);
|
|
1704
1727
|
}
|
|
1705
1728
|
case "stop": {
|
|
@@ -1720,7 +1743,7 @@ async function handleProject(action, args, config) {
|
|
|
1720
1743
|
const key = args.key;
|
|
1721
1744
|
if (!key)
|
|
1722
1745
|
throw new GodotMCPError("No key specified", "INVALID_ARGS", 'Provide key (e.g., "application/config/name").');
|
|
1723
|
-
const configPath = join5(
|
|
1746
|
+
const configPath = join5(resolve9(projectPath), "project.godot");
|
|
1724
1747
|
if (!existsSync9(configPath))
|
|
1725
1748
|
throw new GodotMCPError("No project.godot found", "PROJECT_NOT_FOUND", "Verify the project path.");
|
|
1726
1749
|
const settings = await parseProjectSettingsAsync(configPath);
|
|
@@ -1734,7 +1757,7 @@ async function handleProject(action, args, config) {
|
|
|
1734
1757
|
const value = args.value;
|
|
1735
1758
|
if (!key || value === void 0)
|
|
1736
1759
|
throw new GodotMCPError("key and value required", "INVALID_ARGS", "Provide key and value.");
|
|
1737
|
-
const configPath = join5(
|
|
1760
|
+
const configPath = join5(resolve9(projectPath), "project.godot");
|
|
1738
1761
|
if (!existsSync9(configPath))
|
|
1739
1762
|
throw new GodotMCPError("No project.godot found", "PROJECT_NOT_FOUND", "Verify the project path.");
|
|
1740
1763
|
const content = await readFile2(configPath, "utf-8");
|
|
@@ -1759,10 +1782,10 @@ async function handleProject(action, args, config) {
|
|
|
1759
1782
|
const result = execGodotSync(config.godotPath, [
|
|
1760
1783
|
"--headless",
|
|
1761
1784
|
"--path",
|
|
1762
|
-
|
|
1785
|
+
resolve9(projectPath),
|
|
1763
1786
|
"--export-release",
|
|
1764
1787
|
preset,
|
|
1765
|
-
|
|
1788
|
+
safeResolve(projectPath, outputPath)
|
|
1766
1789
|
]);
|
|
1767
1790
|
return formatSuccess(`Export complete: ${outputPath}
|
|
1768
1791
|
${result.stdout}`);
|
|
@@ -1778,12 +1801,11 @@ ${result.stdout}`);
|
|
|
1778
1801
|
|
|
1779
1802
|
// src/tools/composite/resources.ts
|
|
1780
1803
|
import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync10, statSync, unlinkSync } from "node:fs";
|
|
1781
|
-
import { extname, join as join6, relative, resolve as
|
|
1804
|
+
import { extname, join as join6, relative as relative2, resolve as resolve10 } from "node:path";
|
|
1782
1805
|
var RESOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1783
1806
|
".tres",
|
|
1784
1807
|
".res",
|
|
1785
1808
|
".tscn",
|
|
1786
|
-
".tscn",
|
|
1787
1809
|
".png",
|
|
1788
1810
|
".jpg",
|
|
1789
1811
|
".jpeg",
|
|
@@ -1798,19 +1820,17 @@ var RESOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
1798
1820
|
".gdshaderinc",
|
|
1799
1821
|
".import"
|
|
1800
1822
|
]);
|
|
1801
|
-
function findResourceFiles(dir, extensions) {
|
|
1823
|
+
function findResourceFiles(dir, extensions, results = []) {
|
|
1802
1824
|
const exts = extensions || RESOURCE_EXTENSIONS;
|
|
1803
|
-
const results = [];
|
|
1804
1825
|
try {
|
|
1805
|
-
const entries = readdirSync2(dir);
|
|
1826
|
+
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
1806
1827
|
for (const entry of entries) {
|
|
1807
|
-
if (entry.startsWith(".") || entry === "node_modules" || entry === "build") continue;
|
|
1808
|
-
const fullPath = join6(dir, entry);
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
results.push({ path: fullPath, size: stat.size });
|
|
1828
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "build") continue;
|
|
1829
|
+
const fullPath = join6(dir, entry.name);
|
|
1830
|
+
if (entry.isDirectory()) {
|
|
1831
|
+
findResourceFiles(fullPath, exts, results);
|
|
1832
|
+
} else if (exts.has(extname(entry.name).toLowerCase())) {
|
|
1833
|
+
results.push({ path: fullPath, size: statSync(fullPath).size });
|
|
1814
1834
|
}
|
|
1815
1835
|
}
|
|
1816
1836
|
} catch {
|
|
@@ -1822,7 +1842,7 @@ async function handleResources(action, args, config) {
|
|
|
1822
1842
|
switch (action) {
|
|
1823
1843
|
case "list": {
|
|
1824
1844
|
if (!projectPath) throw new GodotMCPError("No project path specified", "INVALID_ARGS", "Provide project_path.");
|
|
1825
|
-
const resolvedPath =
|
|
1845
|
+
const resolvedPath = resolve10(projectPath);
|
|
1826
1846
|
const filterType = args.type;
|
|
1827
1847
|
let exts;
|
|
1828
1848
|
if (filterType) {
|
|
@@ -1838,7 +1858,7 @@ async function handleResources(action, args, config) {
|
|
|
1838
1858
|
}
|
|
1839
1859
|
const resources = findResourceFiles(resolvedPath, exts);
|
|
1840
1860
|
const relativePaths = resources.map((r) => ({
|
|
1841
|
-
path:
|
|
1861
|
+
path: relative2(resolvedPath, r.path).replace(/\\/g, "/"),
|
|
1842
1862
|
ext: extname(r.path),
|
|
1843
1863
|
size: r.size
|
|
1844
1864
|
}));
|
|
@@ -1847,7 +1867,7 @@ async function handleResources(action, args, config) {
|
|
|
1847
1867
|
case "info": {
|
|
1848
1868
|
const resPath = args.resource_path;
|
|
1849
1869
|
if (!resPath) throw new GodotMCPError("No resource_path specified", "INVALID_ARGS", "Provide resource_path.");
|
|
1850
|
-
const fullPath = projectPath ?
|
|
1870
|
+
const fullPath = projectPath ? safeResolve(projectPath, resPath) : resolve10(resPath);
|
|
1851
1871
|
if (!existsSync10(fullPath))
|
|
1852
1872
|
throw new GodotMCPError(`Resource not found: ${resPath}`, "RESOURCE_ERROR", "Check the file path.");
|
|
1853
1873
|
const stat = statSync(fullPath);
|
|
@@ -1870,7 +1890,7 @@ async function handleResources(action, args, config) {
|
|
|
1870
1890
|
case "delete": {
|
|
1871
1891
|
const resPath = args.resource_path;
|
|
1872
1892
|
if (!resPath) throw new GodotMCPError("No resource_path specified", "INVALID_ARGS", "Provide resource_path.");
|
|
1873
|
-
const fullPath = projectPath ?
|
|
1893
|
+
const fullPath = projectPath ? safeResolve(projectPath, resPath) : resolve10(resPath);
|
|
1874
1894
|
if (!existsSync10(fullPath))
|
|
1875
1895
|
throw new GodotMCPError(`Resource not found: ${resPath}`, "RESOURCE_ERROR", "Check the file path.");
|
|
1876
1896
|
unlinkSync(fullPath);
|
|
@@ -1881,7 +1901,7 @@ async function handleResources(action, args, config) {
|
|
|
1881
1901
|
case "import_config": {
|
|
1882
1902
|
const resPath = args.resource_path;
|
|
1883
1903
|
if (!resPath) throw new GodotMCPError("No resource_path specified", "INVALID_ARGS", "Provide resource_path.");
|
|
1884
|
-
const importPath = projectPath ?
|
|
1904
|
+
const importPath = projectPath ? safeResolve(projectPath, `${resPath}.import`) : resolve10(`${resPath}.import`);
|
|
1885
1905
|
if (!existsSync10(importPath)) {
|
|
1886
1906
|
return formatJSON({ path: resPath, imported: false, message: "No .import file found." });
|
|
1887
1907
|
}
|
|
@@ -1900,18 +1920,9 @@ ${content}`);
|
|
|
1900
1920
|
}
|
|
1901
1921
|
|
|
1902
1922
|
// src/tools/composite/scenes.ts
|
|
1903
|
-
import {
|
|
1904
|
-
copyFileSync,
|
|
1905
|
-
existsSync as existsSync11,
|
|
1906
|
-
mkdirSync,
|
|
1907
|
-
readdirSync as readdirSync3,
|
|
1908
|
-
readFileSync as readFileSync11,
|
|
1909
|
-
statSync as statSync2,
|
|
1910
|
-
unlinkSync as unlinkSync2,
|
|
1911
|
-
writeFileSync as writeFileSync9
|
|
1912
|
-
} from "node:fs";
|
|
1923
|
+
import { copyFileSync, existsSync as existsSync11, mkdirSync, readdirSync as readdirSync3, readFileSync as readFileSync11, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "node:fs";
|
|
1913
1924
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
1914
|
-
import { basename, dirname, extname as extname2, join as join7, relative as
|
|
1925
|
+
import { basename, dirname, extname as extname2, join as join7, relative as relative3, resolve as resolve11 } from "node:path";
|
|
1915
1926
|
async function parseTscnFile(filePath) {
|
|
1916
1927
|
const content = await readFile3(filePath, "utf-8");
|
|
1917
1928
|
const lines = content.split("\n");
|
|
@@ -1951,18 +1962,15 @@ async function parseTscnFile(filePath) {
|
|
|
1951
1962
|
}
|
|
1952
1963
|
return { path: filePath, rootNode, rootType, nodeCount: nodes.length, nodes, resources };
|
|
1953
1964
|
}
|
|
1954
|
-
function findSceneFiles(dir) {
|
|
1955
|
-
const results = [];
|
|
1965
|
+
function findSceneFiles(dir, results = []) {
|
|
1956
1966
|
try {
|
|
1957
|
-
const entries = readdirSync3(dir);
|
|
1967
|
+
const entries = readdirSync3(dir, { withFileTypes: true });
|
|
1958
1968
|
for (const entry of entries) {
|
|
1959
|
-
if (entry.startsWith(".") || entry === "node_modules" || entry === "build") continue;
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
if (
|
|
1963
|
-
results.push(
|
|
1964
|
-
} else if (extname2(entry) === ".tscn") {
|
|
1965
|
-
results.push(fullPath);
|
|
1969
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "build") continue;
|
|
1970
|
+
if (entry.isDirectory()) {
|
|
1971
|
+
findSceneFiles(join7(dir, entry.name), results);
|
|
1972
|
+
} else if (extname2(entry.name) === ".tscn") {
|
|
1973
|
+
results.push(join7(dir, entry.name));
|
|
1966
1974
|
}
|
|
1967
1975
|
}
|
|
1968
1976
|
} catch {
|
|
@@ -1992,8 +2000,9 @@ function validateSceneArgs(action, args, config) {
|
|
|
1992
2000
|
}
|
|
1993
2001
|
return { projectPath, scenePath, newPath };
|
|
1994
2002
|
}
|
|
1995
|
-
function resolvePath(base,
|
|
1996
|
-
|
|
2003
|
+
function resolvePath(base, relativePath) {
|
|
2004
|
+
if (base) return safeResolve(base, relativePath);
|
|
2005
|
+
return resolve11(relativePath);
|
|
1997
2006
|
}
|
|
1998
2007
|
async function handleScenes(action, args, config) {
|
|
1999
2008
|
const { projectPath, scenePath, newPath } = validateSceneArgs(action, args, config);
|
|
@@ -2001,7 +2010,7 @@ async function handleScenes(action, args, config) {
|
|
|
2001
2010
|
case "create": {
|
|
2002
2011
|
const rootType = args.root_type || "Node2D";
|
|
2003
2012
|
const rootName = args.root_name || basename(scenePath, ".tscn");
|
|
2004
|
-
const fullPath =
|
|
2013
|
+
const fullPath = resolve11(projectPath, scenePath);
|
|
2005
2014
|
if (existsSync11(fullPath)) {
|
|
2006
2015
|
throw new GodotMCPError(
|
|
2007
2016
|
`Scene already exists: ${scenePath}`,
|
|
@@ -2016,9 +2025,9 @@ async function handleScenes(action, args, config) {
|
|
|
2016
2025
|
Root: ${rootName} (${rootType})`);
|
|
2017
2026
|
}
|
|
2018
2027
|
case "list": {
|
|
2019
|
-
const resolvedPath =
|
|
2028
|
+
const resolvedPath = resolve11(projectPath);
|
|
2020
2029
|
const scenes = findSceneFiles(resolvedPath);
|
|
2021
|
-
const relativePaths = scenes.map((s) =>
|
|
2030
|
+
const relativePaths = scenes.map((s) => relative3(resolvedPath, s).replace(/\\/g, "/"));
|
|
2022
2031
|
return formatJSON({
|
|
2023
2032
|
project: resolvedPath,
|
|
2024
2033
|
count: relativePaths.length,
|
|
@@ -2059,7 +2068,7 @@ Root: ${rootName} (${rootType})`);
|
|
|
2059
2068
|
return formatSuccess(`Duplicated: ${scenePath} -> ${newPath}`);
|
|
2060
2069
|
}
|
|
2061
2070
|
case "set_main": {
|
|
2062
|
-
const configPath = join7(
|
|
2071
|
+
const configPath = join7(resolve11(projectPath), "project.godot");
|
|
2063
2072
|
if (!existsSync11(configPath)) {
|
|
2064
2073
|
throw new GodotMCPError("No project.godot found", "PROJECT_NOT_FOUND", "Verify the project path.");
|
|
2065
2074
|
}
|
|
@@ -2079,26 +2088,8 @@ Root: ${rootName} (${rootType})`);
|
|
|
2079
2088
|
}
|
|
2080
2089
|
|
|
2081
2090
|
// src/tools/composite/scripts.ts
|
|
2082
|
-
import { existsSync as existsSync12, mkdirSync as mkdirSync2, readdirSync as readdirSync4, readFileSync as readFileSync12,
|
|
2091
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync2, readdirSync as readdirSync4, readFileSync as readFileSync12, unlinkSync as unlinkSync3, writeFileSync as writeFileSync10 } from "node:fs";
|
|
2083
2092
|
import { dirname as dirname2, extname as extname3, join as join8, relative as relative4, resolve as resolve12 } from "node:path";
|
|
2084
|
-
|
|
2085
|
-
// src/tools/helpers/paths.ts
|
|
2086
|
-
import { isAbsolute, relative as relative3, resolve as resolve11 } from "node:path";
|
|
2087
|
-
function safeResolve(baseDir, targetPath) {
|
|
2088
|
-
const resolvedBase = resolve11(baseDir);
|
|
2089
|
-
const resolvedTarget = resolve11(resolvedBase, targetPath);
|
|
2090
|
-
const relativePath = relative3(resolvedBase, resolvedTarget);
|
|
2091
|
-
if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
2092
|
-
throw new GodotMCPError(
|
|
2093
|
-
`Access denied: Path '${targetPath}' resolves to '${resolvedTarget}' which is outside the project root '${resolvedBase}'.`,
|
|
2094
|
-
"INVALID_ARGS",
|
|
2095
|
-
"Ensure all file paths are within the project directory."
|
|
2096
|
-
);
|
|
2097
|
-
}
|
|
2098
|
-
return resolvedTarget;
|
|
2099
|
-
}
|
|
2100
|
-
|
|
2101
|
-
// src/tools/composite/scripts.ts
|
|
2102
2093
|
var SCRIPT_TEMPLATES = {
|
|
2103
2094
|
Node: `extends Node
|
|
2104
2095
|
|
|
@@ -2192,15 +2183,14 @@ func _ready() -> void:
|
|
|
2192
2183
|
}
|
|
2193
2184
|
function findScriptFiles(dir, results = []) {
|
|
2194
2185
|
try {
|
|
2195
|
-
const entries = readdirSync4(dir);
|
|
2186
|
+
const entries = readdirSync4(dir, { withFileTypes: true });
|
|
2196
2187
|
for (const entry of entries) {
|
|
2197
|
-
if (entry.startsWith(".") || entry === "node_modules" || entry === "build" || entry === "addons")
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
results.push(fullPath);
|
|
2188
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "build" || entry.name === "addons")
|
|
2189
|
+
continue;
|
|
2190
|
+
if (entry.isDirectory()) {
|
|
2191
|
+
findScriptFiles(join8(dir, entry.name), results);
|
|
2192
|
+
} else if (extname3(entry.name) === ".gd") {
|
|
2193
|
+
results.push(join8(dir, entry.name));
|
|
2204
2194
|
}
|
|
2205
2195
|
}
|
|
2206
2196
|
} catch {
|
|
@@ -2443,7 +2433,7 @@ async function handleShader(action, args, config) {
|
|
|
2443
2433
|
);
|
|
2444
2434
|
const shaderType = args.shader_type || "canvas_item";
|
|
2445
2435
|
const content = args.content || SHADER_TEMPLATES[shaderType] || SHADER_TEMPLATES.canvas_item;
|
|
2446
|
-
const fullPath = projectPath ?
|
|
2436
|
+
const fullPath = projectPath ? safeResolve(projectPath, shaderPath) : resolve13(shaderPath);
|
|
2447
2437
|
await mkdir(dirname3(fullPath), { recursive: true });
|
|
2448
2438
|
try {
|
|
2449
2439
|
await writeFile3(fullPath, content, { encoding: "utf-8", flag: "wx" });
|
|
@@ -2458,7 +2448,7 @@ async function handleShader(action, args, config) {
|
|
|
2458
2448
|
case "read": {
|
|
2459
2449
|
const shaderPath = args.shader_path;
|
|
2460
2450
|
if (!shaderPath) throw new GodotMCPError("No shader_path specified", "INVALID_ARGS", "Provide shader_path.");
|
|
2461
|
-
const fullPath = projectPath ?
|
|
2451
|
+
const fullPath = projectPath ? safeResolve(projectPath, shaderPath) : resolve13(shaderPath);
|
|
2462
2452
|
try {
|
|
2463
2453
|
const content = await readFile4(fullPath, "utf-8");
|
|
2464
2454
|
return formatSuccess(`File: ${shaderPath}
|
|
@@ -2476,7 +2466,7 @@ ${content}`);
|
|
|
2476
2466
|
if (!shaderPath) throw new GodotMCPError("No shader_path specified", "INVALID_ARGS", "Provide shader_path.");
|
|
2477
2467
|
const content = args.content;
|
|
2478
2468
|
if (!content) throw new GodotMCPError("No content specified", "INVALID_ARGS", "Provide shader content.");
|
|
2479
|
-
const fullPath = projectPath ?
|
|
2469
|
+
const fullPath = projectPath ? safeResolve(projectPath, shaderPath) : resolve13(shaderPath);
|
|
2480
2470
|
await mkdir(dirname3(fullPath), { recursive: true });
|
|
2481
2471
|
await writeFile3(fullPath, content, "utf-8");
|
|
2482
2472
|
return formatSuccess(`Written: ${shaderPath} (${content.length} chars)`);
|
|
@@ -2484,7 +2474,7 @@ ${content}`);
|
|
|
2484
2474
|
case "get_params": {
|
|
2485
2475
|
const shaderPath = args.shader_path;
|
|
2486
2476
|
if (!shaderPath) throw new GodotMCPError("No shader_path specified", "INVALID_ARGS", "Provide shader_path.");
|
|
2487
|
-
const fullPath = projectPath ?
|
|
2477
|
+
const fullPath = projectPath ? safeResolve(projectPath, shaderPath) : resolve13(shaderPath);
|
|
2488
2478
|
try {
|
|
2489
2479
|
const content = await readFile4(fullPath, "utf-8");
|
|
2490
2480
|
const params = [];
|
|
@@ -2533,7 +2523,7 @@ async function handleSignals(action, args, config) {
|
|
|
2533
2523
|
const projectPath = args.project_path || config.projectPath;
|
|
2534
2524
|
const scenePath = args.scene_path;
|
|
2535
2525
|
if (!scenePath) throw new GodotMCPError("No scene_path specified", "INVALID_ARGS", "Provide scene_path.");
|
|
2536
|
-
const fullPath = projectPath ?
|
|
2526
|
+
const fullPath = projectPath ? safeResolve(projectPath, scenePath) : resolve14(scenePath);
|
|
2537
2527
|
async function readScene() {
|
|
2538
2528
|
try {
|
|
2539
2529
|
return await readFile5(fullPath, "utf-8");
|
|
@@ -2647,7 +2637,7 @@ async function handleTilemap(action, args, config) {
|
|
|
2647
2637
|
'Provide tileset_path (e.g., "tilesets/main.tres").'
|
|
2648
2638
|
);
|
|
2649
2639
|
const tileSize = args.tile_size || 16;
|
|
2650
|
-
const fullPath = projectPath ?
|
|
2640
|
+
const fullPath = projectPath ? safeResolve(projectPath, tilesetPath) : resolve15(tilesetPath);
|
|
2651
2641
|
if (existsSync14(fullPath)) {
|
|
2652
2642
|
throw new GodotMCPError(`TileSet already exists: ${tilesetPath}`, "TILEMAP_ERROR", "Use a different path.");
|
|
2653
2643
|
}
|
|
@@ -2669,7 +2659,7 @@ async function handleTilemap(action, args, config) {
|
|
|
2669
2659
|
if (!tilesetPath || !texturePath) {
|
|
2670
2660
|
throw new GodotMCPError("tileset_path and texture_path required", "INVALID_ARGS", "Both are required.");
|
|
2671
2661
|
}
|
|
2672
|
-
const fullPath = projectPath ?
|
|
2662
|
+
const fullPath = projectPath ? safeResolve(projectPath, tilesetPath) : resolve15(tilesetPath);
|
|
2673
2663
|
if (!existsSync14(fullPath))
|
|
2674
2664
|
throw new GodotMCPError(`TileSet not found: ${tilesetPath}`, "TILEMAP_ERROR", "Create the tileset first.");
|
|
2675
2665
|
let content = readFileSync13(fullPath, "utf-8");
|
|
@@ -2699,7 +2689,7 @@ async function handleTilemap(action, args, config) {
|
|
|
2699
2689
|
case "list": {
|
|
2700
2690
|
const scenePath = args.scene_path;
|
|
2701
2691
|
if (!scenePath) throw new GodotMCPError("No scene_path specified", "INVALID_ARGS", "Provide scene_path.");
|
|
2702
|
-
const fullPath = projectPath ?
|
|
2692
|
+
const fullPath = projectPath ? safeResolve(projectPath, scenePath) : resolve15(scenePath);
|
|
2703
2693
|
if (!existsSync14(fullPath))
|
|
2704
2694
|
throw new GodotMCPError(`Scene not found: ${scenePath}`, "SCENE_ERROR", "Check the file path.");
|
|
2705
2695
|
const content = readFileSync13(fullPath, "utf-8");
|
|
@@ -2743,7 +2733,7 @@ var CONTROL_TEMPLATES = {
|
|
|
2743
2733
|
GridContainer: { columns: "2" }
|
|
2744
2734
|
};
|
|
2745
2735
|
function resolveScene3(projectPath, scenePath) {
|
|
2746
|
-
const fullPath = projectPath ?
|
|
2736
|
+
const fullPath = projectPath ? safeResolve(projectPath, scenePath) : resolve16(scenePath);
|
|
2747
2737
|
if (!existsSync15(fullPath))
|
|
2748
2738
|
throw new GodotMCPError(`Scene not found: ${scenePath}`, "SCENE_ERROR", "Check the file path.");
|
|
2749
2739
|
return fullPath;
|
|
@@ -2791,7 +2781,7 @@ ${nodeDecl}`;
|
|
|
2791
2781
|
"INVALID_ARGS",
|
|
2792
2782
|
'Provide theme_path (e.g., "themes/main.tres").'
|
|
2793
2783
|
);
|
|
2794
|
-
const fullPath = projectPath ?
|
|
2784
|
+
const fullPath = projectPath ? safeResolve(projectPath, themePath) : resolve16(themePath);
|
|
2795
2785
|
const fontSize = args.font_size || 16;
|
|
2796
2786
|
const _fontColor = args.font_color || "Color(1, 1, 1, 1)";
|
|
2797
2787
|
const _bgColor = args.bg_color || "Color(0.15, 0.15, 0.15, 1)";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"animation.d.ts","sourceRoot":"","sources":["../../../../src/tools/composite/animation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"animation.d.ts","sourceRoot":"","sources":["../../../../src/tools/composite/animation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAWvD,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,WAAW;;;;;GAuIvG"}
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
6
|
import { resolve } from 'node:path';
|
|
7
7
|
import { formatJSON, formatSuccess, GodotMCPError } from '../helpers/errors.js';
|
|
8
|
+
import { safeResolve } from '../helpers/paths.js';
|
|
8
9
|
function resolveScene(projectPath, scenePath) {
|
|
9
|
-
const fullPath = projectPath ?
|
|
10
|
+
const fullPath = projectPath ? safeResolve(projectPath, scenePath) : resolve(scenePath);
|
|
10
11
|
if (!existsSync(fullPath))
|
|
11
12
|
throw new GodotMCPError(`Scene not found: ${scenePath}`, 'SCENE_ERROR', 'Check the file path.');
|
|
12
13
|
return fullPath;
|