godot-daedalus_backend 1.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.
Files changed (67) hide show
  1. package/README.md +101 -0
  2. package/bin/godot-daedalus-backend.js +4 -0
  3. package/bin/godot-daedalus-mcp.js +4 -0
  4. package/bin/godot-daedalus-terminal-mcp.js +4 -0
  5. package/bin/run-tsx-entry.js +26 -0
  6. package/package.json +54 -0
  7. package/scripts/deepseek-tokenizer-server.py +54 -0
  8. package/src/app-paths.ts +36 -0
  9. package/src/main.ts +21 -0
  10. package/src/mcp/content-length-protocol.ts +68 -0
  11. package/src/mcp/custom-mcp-config-store.ts +397 -0
  12. package/src/mcp/godot-diagnostics-bridge.ts +1298 -0
  13. package/src/mcp/godot-editor-bridge.ts +307 -0
  14. package/src/mcp/godot-mcp-server.ts +3484 -0
  15. package/src/mcp/godot-paths.ts +151 -0
  16. package/src/mcp/godot-project-settings.ts +233 -0
  17. package/src/mcp/godot-tool-registration.ts +46 -0
  18. package/src/mcp/mcp-config.ts +48 -0
  19. package/src/mcp/mcp-host.ts +393 -0
  20. package/src/mcp/mcp-session.ts +81 -0
  21. package/src/mcp/terminal-mcp-server.ts +576 -0
  22. package/src/mcp/tscn-tools.ts +302 -0
  23. package/src/mcp/types.ts +12 -0
  24. package/src/ping-client.ts +24 -0
  25. package/src/prompts/registry.ts +97 -0
  26. package/src/prompts/templates/backend-helper.md +25 -0
  27. package/src/prompts/templates/gdscript-reviewer.md +19 -0
  28. package/src/prompts/templates/godot-assistant.md +225 -0
  29. package/src/prompts/templates/scene-architect.md +15 -0
  30. package/src/prompts/templates/session-compressor.md +33 -0
  31. package/src/protocol/schema.ts +486 -0
  32. package/src/protocol/types.ts +77 -0
  33. package/src/providers/deepseek-agent.ts +1014 -0
  34. package/src/providers/deepseek-client.ts +114 -0
  35. package/src/providers/deepseek-dsml-tools.ts +90 -0
  36. package/src/providers/deepseek-loose-tools.ts +450 -0
  37. package/src/providers/provider-config-store.ts +164 -0
  38. package/src/server/client-session.ts +93 -0
  39. package/src/server/request-dispatcher.ts +74 -0
  40. package/src/server/response-helpers.ts +33 -0
  41. package/src/server/send-json.ts +8 -0
  42. package/src/server/websocket-server.ts +3997 -0
  43. package/src/session/session-compressor.ts +68 -0
  44. package/src/session/session-store.ts +669 -0
  45. package/src/skills/registry.ts +180 -0
  46. package/src/skills/templates/backend-helper.md +12 -0
  47. package/src/skills/templates/file-creator.md +14 -0
  48. package/src/skills/templates/gdscript-review.md +12 -0
  49. package/src/skills/templates/godot-project-init.md +29 -0
  50. package/src/skills/templates/scene-builder.md +12 -0
  51. package/src/tokens/deepseek-tokenizer-counter.ts +233 -0
  52. package/src/tokens/model-profiles.ts +38 -0
  53. package/src/tokens/token-counter-factory.ts +52 -0
  54. package/src/tokens/token-counter.ts +22 -0
  55. package/src/tools/approval-gateway.ts +111 -0
  56. package/src/tools/llm-tools.ts +1415 -0
  57. package/src/tools/tool-dispatcher.ts +147 -0
  58. package/src/tools/tool-event-describer.ts +387 -0
  59. package/src/tools/tool-idempotency.ts +373 -0
  60. package/src/tools/tool-policy-table.ts +61 -0
  61. package/src/tools/tool-policy.ts +73 -0
  62. package/src/workflow/llm-planner.ts +407 -0
  63. package/src/workflow/planner.ts +201 -0
  64. package/src/workflow/runner.ts +141 -0
  65. package/src/workflow/types.ts +69 -0
  66. package/src/workspace/registry.ts +104 -0
  67. package/src/workspace/types.ts +7 -0
@@ -0,0 +1,151 @@
1
+ import * as path from "node:path";
2
+
3
+ export const WRITABLE_EXTENSIONS: ReadonlySet<string> = new Set([
4
+ ".gd",
5
+ ".tres",
6
+ ".tscn",
7
+ ".json",
8
+ ".md",
9
+ ".txt"
10
+ ]);
11
+
12
+ export type ResolvedGodotPath = {
13
+ originalPath: string;
14
+ absolutePath: string;
15
+ rootPath: string;
16
+ kind: "user" | "res" | "absolute" | "relative_user";
17
+ };
18
+
19
+ export type GodotPathContext = {
20
+ projectRoot: string;
21
+ appDataPath: string;
22
+ userProfilePath?: string | undefined;
23
+ projectName: string;
24
+ };
25
+
26
+ export function isPathInsideRoot(absolutePath: string, rootPath: string): boolean {
27
+ const relativePath: string = path.relative(path.resolve(rootPath), path.resolve(absolutePath));
28
+ return relativePath.length === 0 || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath));
29
+ }
30
+
31
+ export function normalizeDisplayPath(value: string): string {
32
+ return value.replaceAll("\\", "/");
33
+ }
34
+
35
+ export function sanitizeGodotUserDirName(value: string): string {
36
+ return value.replace(/[<>:"/\\|?*\x00-\x1F]/g, "_").trim() || "GodotProject";
37
+ }
38
+
39
+ export function getGodotUserDataDir(context: GodotPathContext): string {
40
+ return path.join(context.appDataPath, "Godot", "app_userdata", sanitizeGodotUserDirName(context.projectName));
41
+ }
42
+
43
+ export function resolveProjectPath(projectRoot: string, relativePath: string): string {
44
+ const cleanedPath: string = relativePath.trim();
45
+ const resolvedPath: string = path.resolve(projectRoot, cleanedPath.length > 0 ? cleanedPath : ".");
46
+
47
+ if (!isPathInsideRoot(resolvedPath, projectRoot)) {
48
+ throw new Error(`Path traversal denied: ${relativePath}`);
49
+ }
50
+
51
+ return resolvedPath;
52
+ }
53
+
54
+ export function resolveGodotPath(resourcePath: string, context: GodotPathContext): ResolvedGodotPath {
55
+ const trimmedPath: string = resourcePath.trim();
56
+ const userDataDir: string = getGodotUserDataDir(context);
57
+
58
+ if (trimmedPath.startsWith("user://")) {
59
+ const relativeUserPath: string = trimmedPath.slice("user://".length);
60
+ const absolutePath: string = path.resolve(userDataDir, relativeUserPath);
61
+ if (!isPathInsideRoot(absolutePath, userDataDir)) {
62
+ throw new Error(`user:// path traversal denied: ${resourcePath}`);
63
+ }
64
+ return { originalPath: resourcePath, absolutePath, rootPath: userDataDir, kind: "user" };
65
+ }
66
+
67
+ if (trimmedPath.startsWith("res://")) {
68
+ const relativeResPath: string = trimmedPath.slice("res://".length);
69
+ const absolutePath: string = path.resolve(context.projectRoot, relativeResPath);
70
+ if (!isPathInsideRoot(absolutePath, context.projectRoot)) {
71
+ throw new Error(`res:// path traversal denied: ${resourcePath}`);
72
+ }
73
+ return { originalPath: resourcePath, absolutePath, rootPath: context.projectRoot, kind: "res" };
74
+ }
75
+
76
+ if (path.isAbsolute(trimmedPath)) {
77
+ const absolutePath: string = path.resolve(trimmedPath);
78
+ if (isPathInsideRoot(absolutePath, context.projectRoot)) {
79
+ return { originalPath: resourcePath, absolutePath, rootPath: context.projectRoot, kind: "absolute" };
80
+ }
81
+ if (isPathInsideRoot(absolutePath, userDataDir)) {
82
+ return { originalPath: resourcePath, absolutePath, rootPath: userDataDir, kind: "absolute" };
83
+ }
84
+ throw new Error(`Absolute path is outside allowed Godot project/user data roots: ${resourcePath}`);
85
+ }
86
+
87
+ const absolutePath: string = path.resolve(userDataDir, trimmedPath);
88
+ if (!isPathInsideRoot(absolutePath, userDataDir)) {
89
+ throw new Error(`Relative user data path traversal denied: ${resourcePath}`);
90
+ }
91
+ return { originalPath: resourcePath, absolutePath, rootPath: userDataDir, kind: "relative_user" };
92
+ }
93
+
94
+ export function redactOnePath(value: string, context: GodotPathContext, raw: boolean): string {
95
+ if (raw) {
96
+ return normalizeDisplayPath(value);
97
+ }
98
+
99
+ const normalizedValue: string = normalizeDisplayPath(path.resolve(value));
100
+ const normalizedProjectRoot: string = normalizeDisplayPath(path.resolve(context.projectRoot));
101
+ if (normalizedValue === normalizedProjectRoot || normalizedValue.startsWith(`${normalizedProjectRoot}/`)) {
102
+ return normalizedValue;
103
+ }
104
+
105
+ if (context.userProfilePath !== undefined) {
106
+ const normalizedUserProfile: string = normalizeDisplayPath(path.resolve(context.userProfilePath));
107
+ if (normalizedValue === normalizedUserProfile || normalizedValue.startsWith(`${normalizedUserProfile}/`)) {
108
+ return normalizedValue.replace(normalizedUserProfile, "[user]");
109
+ }
110
+ }
111
+
112
+ return `[redacted]/${path.basename(normalizedValue)}`;
113
+ }
114
+
115
+ export function redactSensitivePaths(text: string, context: GodotPathContext, raw: boolean): string {
116
+ if (raw) {
117
+ return normalizeDisplayPath(text);
118
+ }
119
+
120
+ let redactedText: string = normalizeDisplayPath(text);
121
+ if (context.userProfilePath !== undefined) {
122
+ redactedText = redactedText.replaceAll(normalizeDisplayPath(context.userProfilePath), "[user]");
123
+ }
124
+ return redactedText.replace(/[A-Za-z]:\/[^"\s,)]+/g, (matchedPath: string): string => redactOnePath(matchedPath, context, false));
125
+ }
126
+
127
+ export function assertWritableProjectPath(projectRoot: string, relativePath: string): string {
128
+ const cleanedPath: string = relativePath.trim().replaceAll("\\", "/");
129
+ const resolvedPath: string = resolveProjectPath(projectRoot, cleanedPath);
130
+ const normalizedPath: string = path.relative(projectRoot, resolvedPath).replaceAll(path.sep, "/");
131
+ const segments: string[] = normalizedPath.split("/");
132
+
133
+ for (const segment of segments) {
134
+ if (segment.startsWith(".") && segment !== ".") {
135
+ throw new Error(`Path contains hidden directory: ${segment}`);
136
+ }
137
+ }
138
+
139
+ for (const prefix of [".godot", "addons"]) {
140
+ if (normalizedPath === prefix || normalizedPath.startsWith(`${prefix}/`)) {
141
+ throw new Error(`Writing to ${prefix}/ is not allowed`);
142
+ }
143
+ }
144
+
145
+ const extension: string = path.extname(resolvedPath);
146
+ if (!WRITABLE_EXTENSIONS.has(extension)) {
147
+ throw new Error(`Unsupported writable extension: ${extension || "(none)"}`);
148
+ }
149
+
150
+ return resolvedPath;
151
+ }
@@ -0,0 +1,233 @@
1
+ export const MAX_PROJECT_SETTING_VALUE_CHARS: number = 16 * 1024;
2
+ export const MAX_PROJECT_SETTING_VALUE_LINES: number = 240;
3
+
4
+ export type ProjectSettingEntry = {
5
+ section: string;
6
+ name: string;
7
+ fullKey: string;
8
+ valueExpression: string;
9
+ lineStart: number;
10
+ lineEnd: number;
11
+ };
12
+
13
+ export type ProjectSettingsDocument = {
14
+ content: string;
15
+ lines: string[];
16
+ entries: ProjectSettingEntry[];
17
+ sectionLineIndexes: Map<string, number>;
18
+ };
19
+
20
+ export function normalizeConfigContent(content: string): string {
21
+ return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
22
+ }
23
+
24
+ export function makeProjectSettingFullKey(section: string, name: string): string {
25
+ return `${section}/${name}`;
26
+ }
27
+
28
+ export function splitProjectSettingKey(fullKey: string): { section: string; name: string } {
29
+ const normalizedKey: string = fullKey.trim();
30
+ const slashIndex: number = normalizedKey.lastIndexOf("/");
31
+ if (slashIndex <= 0 || slashIndex === normalizedKey.length - 1) {
32
+ throw new Error(`Invalid project setting key: ${fullKey}`);
33
+ }
34
+
35
+ return {
36
+ section: normalizedKey.slice(0, slashIndex),
37
+ name: normalizedKey.slice(slashIndex + 1)
38
+ };
39
+ }
40
+
41
+ export function getExpressionBalance(text: string): number {
42
+ let balance: number = 0;
43
+ let inString: boolean = false;
44
+ let escaped: boolean = false;
45
+
46
+ for (const char of text) {
47
+ if (escaped) {
48
+ escaped = false;
49
+ continue;
50
+ }
51
+ if (char === "\\") {
52
+ escaped = true;
53
+ continue;
54
+ }
55
+ if (char === "\"") {
56
+ inString = !inString;
57
+ continue;
58
+ }
59
+ if (inString) {
60
+ continue;
61
+ }
62
+ if (char === "(" || char === "[" || char === "{") {
63
+ balance += 1;
64
+ } else if (char === ")" || char === "]" || char === "}") {
65
+ balance -= 1;
66
+ }
67
+ }
68
+
69
+ return balance;
70
+ }
71
+
72
+ export function normalizeProjectSettingValueExpression(valueExpression: string): string {
73
+ const normalizedValue: string = normalizeConfigContent(valueExpression).trim();
74
+ if (normalizedValue.length === 0) {
75
+ throw new Error("valueExpression must not be empty");
76
+ }
77
+ if (normalizedValue.length > MAX_PROJECT_SETTING_VALUE_CHARS) {
78
+ throw new Error(`valueExpression too large: ${normalizedValue.length} chars`);
79
+ }
80
+
81
+ const valueLines: string[] = normalizedValue.split("\n");
82
+ if (valueLines.length > MAX_PROJECT_SETTING_VALUE_LINES) {
83
+ throw new Error(`valueExpression has too many lines: ${valueLines.length}`);
84
+ }
85
+ for (const line of valueLines) {
86
+ if (/^\s*\[.+\]\s*$/.test(line)) {
87
+ throw new Error("valueExpression must not contain project.godot section headers");
88
+ }
89
+ }
90
+ if (getExpressionBalance(normalizedValue) !== 0) {
91
+ throw new Error("valueExpression has unbalanced braces, brackets, or parentheses");
92
+ }
93
+
94
+ return normalizedValue;
95
+ }
96
+
97
+ export function parseProjectSettings(content: string): ProjectSettingsDocument {
98
+ const normalizedContent: string = normalizeConfigContent(content);
99
+ const lines: string[] = normalizedContent.split("\n");
100
+ const entries: ProjectSettingEntry[] = [];
101
+ const sectionLineIndexes: Map<string, number> = new Map();
102
+ let currentSection: string = "";
103
+
104
+ for (let index = 0; index < lines.length; index += 1) {
105
+ const line: string = lines[index] ?? "";
106
+ const sectionMatch: RegExpMatchArray | null = line.match(/^\s*\[([^\]]+)\]\s*$/);
107
+ if (sectionMatch !== null) {
108
+ currentSection = sectionMatch[1] ?? "";
109
+ sectionLineIndexes.set(currentSection, index);
110
+ continue;
111
+ }
112
+
113
+ if (currentSection.length === 0 || line.trim().length === 0 || line.trimStart().startsWith(";")) {
114
+ continue;
115
+ }
116
+
117
+ const equalsIndex: number = line.indexOf("=");
118
+ if (equalsIndex <= 0) {
119
+ continue;
120
+ }
121
+
122
+ const name: string = line.slice(0, equalsIndex).trim();
123
+ let valueExpression: string = line.slice(equalsIndex + 1).trim();
124
+ const lineStart: number = index;
125
+ let lineEnd: number = index;
126
+
127
+ while (getExpressionBalance(valueExpression) > 0 && lineEnd + 1 < lines.length) {
128
+ lineEnd += 1;
129
+ valueExpression += `\n${lines[lineEnd] ?? ""}`;
130
+ }
131
+
132
+ entries.push({
133
+ section: currentSection,
134
+ name,
135
+ fullKey: makeProjectSettingFullKey(currentSection, name),
136
+ valueExpression,
137
+ lineStart,
138
+ lineEnd
139
+ });
140
+ index = lineEnd;
141
+ }
142
+
143
+ return { content: normalizedContent, lines, entries, sectionLineIndexes };
144
+ }
145
+
146
+ export function findProjectSettingEntry(document: ProjectSettingsDocument, fullKey: string): ProjectSettingEntry | undefined {
147
+ return document.entries.find((entry: ProjectSettingEntry): boolean => entry.fullKey === fullKey);
148
+ }
149
+
150
+ function findProjectSettingInsertIndex(document: ProjectSettingsDocument, section: string): number {
151
+ const sectionLineIndex: number | undefined = document.sectionLineIndexes.get(section);
152
+ if (sectionLineIndex === undefined) {
153
+ return document.lines.length;
154
+ }
155
+
156
+ let insertIndex: number = sectionLineIndex + 1;
157
+ for (const entry of document.entries) {
158
+ if (entry.section === section) {
159
+ insertIndex = Math.max(insertIndex, entry.lineEnd + 1);
160
+ }
161
+ }
162
+
163
+ return insertIndex;
164
+ }
165
+
166
+ function createProjectSettingAssignmentLines(name: string, valueExpression: string): string[] {
167
+ const valueLines: string[] = valueExpression.split("\n");
168
+ return valueLines.map((line: string, index: number): string => index === 0 ? `${name}=${line}` : line);
169
+ }
170
+
171
+ function finalizeProjectConfigContent(lines: string[]): string {
172
+ return `${lines.join("\n").replace(/\n+$/g, "")}\n`;
173
+ }
174
+
175
+ export function applyProjectSettingSetToContent(content: string, fullKey: string, valueExpression: string): string {
176
+ const { section, name } = splitProjectSettingKey(fullKey);
177
+ const normalizedValue: string = normalizeProjectSettingValueExpression(valueExpression);
178
+ const document: ProjectSettingsDocument = parseProjectSettings(content);
179
+ const entry: ProjectSettingEntry | undefined = findProjectSettingEntry(document, fullKey);
180
+ const lines: string[] = [...document.lines];
181
+
182
+ if (entry !== undefined) {
183
+ const existingAssignmentLines: string[] = createProjectSettingAssignmentLines(entry.name, normalizedValue);
184
+ lines.splice(entry.lineStart, entry.lineEnd - entry.lineStart + 1, ...existingAssignmentLines);
185
+ return finalizeProjectConfigContent(lines);
186
+ }
187
+
188
+ const assignmentLines: string[] = createProjectSettingAssignmentLines(name, normalizedValue);
189
+ if (!document.sectionLineIndexes.has(section)) {
190
+ if (lines.length > 0 && lines[lines.length - 1] !== "") {
191
+ lines.push("");
192
+ }
193
+ lines.push(`[${section}]`);
194
+ lines.push(...assignmentLines);
195
+ return finalizeProjectConfigContent(lines);
196
+ }
197
+
198
+ lines.splice(findProjectSettingInsertIndex(document, section), 0, ...assignmentLines);
199
+ return finalizeProjectConfigContent(lines);
200
+ }
201
+
202
+ export function applyProjectSettingUnsetToContent(content: string, fullKey: string): string {
203
+ const document: ProjectSettingsDocument = parseProjectSettings(content);
204
+ const entry: ProjectSettingEntry | undefined = findProjectSettingEntry(document, fullKey);
205
+ if (entry === undefined) {
206
+ return finalizeProjectConfigContent(document.lines);
207
+ }
208
+
209
+ const lines: string[] = [...document.lines];
210
+ lines.splice(entry.lineStart, entry.lineEnd - entry.lineStart + 1);
211
+ return finalizeProjectConfigContent(lines);
212
+ }
213
+
214
+ export function proposeProjectSettingSet(content: string, fullKey: string, valueExpression: string): Record<string, unknown> {
215
+ const nextContent: string = applyProjectSettingSetToContent(content, fullKey, valueExpression);
216
+ const document: ProjectSettingsDocument = parseProjectSettings(nextContent);
217
+ return {
218
+ valid: true,
219
+ key: fullKey,
220
+ entry: findProjectSettingEntry(document, fullKey) ?? null,
221
+ size: nextContent.length
222
+ };
223
+ }
224
+
225
+ export function proposeProjectSettingUnset(content: string, fullKey: string): Record<string, unknown> {
226
+ const nextContent: string = applyProjectSettingUnsetToContent(content, fullKey);
227
+ return {
228
+ valid: true,
229
+ key: fullKey,
230
+ removed: findProjectSettingEntry(parseProjectSettings(content), fullKey) !== undefined,
231
+ size: nextContent.length
232
+ };
233
+ }
@@ -0,0 +1,46 @@
1
+ export const GODOT_MCP_TOOL_NAMES: readonly string[] = [
2
+ "get_project_summary",
3
+ "list_project_files",
4
+ "list_scenes",
5
+ "list_scripts",
6
+ "read_text_file",
7
+ "search_text",
8
+ "get_project_log_config",
9
+ "list_project_logs",
10
+ "read_project_log",
11
+ "get_project_settings",
12
+ "get_editor_config_summary",
13
+ "get_editor_settings",
14
+ "list_editor_config_files",
15
+ "read_editor_config_file",
16
+ "get_editor_project_state",
17
+ "get_recent_projects",
18
+ "propose_set_project_setting",
19
+ "set_project_setting",
20
+ "propose_unset_project_setting",
21
+ "unset_project_setting",
22
+ "propose_create_text_file",
23
+ "create_text_file",
24
+ "propose_overwrite_text_file",
25
+ "overwrite_text_file",
26
+ "propose_replace_text_in_file",
27
+ "replace_text_in_file",
28
+ "delete_file",
29
+ "inspect_scene_tree",
30
+ "propose_create_scene",
31
+ "create_scene",
32
+ "propose_add_node_to_scene",
33
+ "add_node_to_scene",
34
+ "propose_attach_script_to_node",
35
+ "attach_script_to_node",
36
+ "propose_connect_signal_in_scene",
37
+ "connect_signal_in_scene",
38
+ "propose_apply_scene_patch",
39
+ "apply_scene_patch"
40
+ ];
41
+
42
+ export const GODOT_MCP_RESOURCE_NAMES: readonly string[] = [
43
+ "project",
44
+ "scenes",
45
+ "scripts"
46
+ ];
@@ -0,0 +1,48 @@
1
+ import type { McpServerConfig } from "./types.js";
2
+ import { getDefaultWorkspace } from "../workspace/registry.js";
3
+ import type { WorkspaceConfig } from "../workspace/types.js";
4
+
5
+ const defaultWs = getDefaultWorkspace();
6
+ const DEFAULT_GODOT_PROJECT_PATH: string | undefined = process.env.GODOT_PROJECT_PATH ?? defaultWs?.rootPath;
7
+ const DEFAULT_GODOT_EXECUTABLE_PATH: string | undefined = process.env.GODOT_EXECUTABLE_PATH ?? defaultWs?.godotExecutablePath;
8
+
9
+ export function buildMcpServerConfigs(workspace?: WorkspaceConfig): McpServerConfig[] {
10
+ const projectPath: string | undefined = workspace?.rootPath ?? DEFAULT_GODOT_PROJECT_PATH;
11
+ const godotPath: string | undefined = workspace?.godotExecutablePath ?? DEFAULT_GODOT_EXECUTABLE_PATH;
12
+
13
+ if (!projectPath) {
14
+ return [];
15
+ }
16
+
17
+ const terminalEnv: Record<string, string> = {
18
+ BACKEND_DIR: process.cwd(),
19
+ GODOT_PROJECT_PATH: projectPath
20
+ };
21
+
22
+ if (godotPath) {
23
+ terminalEnv.GODOT_EXECUTABLE_PATH = godotPath;
24
+ }
25
+
26
+ return [
27
+ {
28
+ id: "godot",
29
+ name: "Godot Project MCP",
30
+ transport: "stdio",
31
+ command: "npx",
32
+ args: ["tsx", "src/mcp/godot-mcp-server.ts"],
33
+ env: {
34
+ GODOT_PROJECT_PATH: projectPath
35
+ }
36
+ },
37
+ {
38
+ id: "terminal",
39
+ name: "Terminal MCP",
40
+ transport: "stdio",
41
+ command: "npx",
42
+ args: ["tsx", "src/mcp/terminal-mcp-server.ts"],
43
+ env: terminalEnv
44
+ }
45
+ ];
46
+ }
47
+
48
+ export const mcpServerConfigs: McpServerConfig[] = buildMcpServerConfigs();