@npgamedev/godot-mcp-server 0.0.1 → 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.
- package/ATTRIBUTIONS.md +141 -0
- package/LICENSE +28 -0
- package/README.md +386 -4
- package/dist/extensions/extensionChanges.js +131 -0
- package/dist/extensions/extensionCommand.js +25 -0
- package/dist/extensions/extensionDiscovery.js +141 -0
- package/dist/extensions/extensionRegistrar.js +82 -0
- package/dist/extensions/extensions.js +33 -0
- package/dist/groups/builtinGroups.js +60 -0
- package/dist/groups/defs/3dTools.js +17 -0
- package/dist/groups/defs/animationAuthoring.js +16 -0
- package/dist/groups/defs/assetOps.js +6 -0
- package/dist/groups/defs/audio.js +6 -0
- package/dist/groups/defs/classdb.js +6 -0
- package/dist/groups/defs/cleanup.js +6 -0
- package/dist/groups/defs/debugger.js +6 -0
- package/dist/groups/defs/editorAdvanced.js +16 -0
- package/dist/groups/defs/inputMap.js +6 -0
- package/dist/groups/defs/layerNaming.js +6 -0
- package/dist/groups/defs/lspCodeAnalysis.js +23 -0
- package/dist/groups/defs/lspCodeNavigation.js +15 -0
- package/dist/groups/defs/navigation.js +17 -0
- package/dist/groups/defs/particles.js +21 -0
- package/dist/groups/defs/pathEditing.js +22 -0
- package/dist/groups/defs/placeholders.js +28 -0
- package/dist/groups/defs/procedural.js +6 -0
- package/dist/groups/defs/resourceIo.js +6 -0
- package/dist/groups/defs/runtimeAdvanced.js +17 -0
- package/dist/groups/defs/sceneAdvanced.js +6 -0
- package/dist/groups/defs/sceneInheritance.js +6 -0
- package/dist/groups/defs/signals.js +6 -0
- package/dist/groups/defs/spriteframes.js +16 -0
- package/dist/groups/defs/theme.js +6 -0
- package/dist/groups/defs/tilemap.js +6 -0
- package/dist/groups/defs/tileset.js +22 -0
- package/dist/groups/defs/tilesetEdit.js +21 -0
- package/dist/groups/defs/userData.js +6 -0
- package/dist/groups/extensionGroups.js +182 -0
- package/dist/groups/groupActivation.js +210 -0
- package/dist/groups/groupCatalogue.js +52 -0
- package/dist/groups/groupMatch.js +182 -0
- package/dist/groups/groupResult.js +16 -0
- package/dist/groups/groupState.js +15 -0
- package/dist/groups/groupToolHandlers.js +103 -0
- package/dist/groups/groupTypes.js +10 -0
- package/dist/groups/groups.js +205 -0
- package/dist/index.js +144 -0
- package/dist/lsp/lspClient.js +509 -0
- package/dist/lsp/lspLabels.js +105 -0
- package/dist/lsp/lspProjectScan.js +156 -0
- package/dist/lsp/lspSession.js +139 -0
- package/dist/lsp/lspStatusReporter.js +76 -0
- package/dist/lsp/lspUri.js +68 -0
- package/dist/mcp/prompts.js +59 -0
- package/dist/mcp/resources.js +113 -0
- package/dist/mcp/roots.js +30 -0
- package/dist/registration/catalogue.js +123 -0
- package/dist/registration/extensionCollision.js +38 -0
- package/dist/registration/operations.js +67 -0
- package/dist/registration/screenshotResponse.js +71 -0
- package/dist/registration/toolDispatch.js +76 -0
- package/dist/registration/toolMeta.js +104 -0
- package/dist/registration/toolRefs.js +48 -0
- package/dist/registration/toolRegistry.js +211 -0
- package/dist/registry.js +291 -0
- package/dist/registryLiveness.js +113 -0
- package/dist/security/pathGuard.js +97 -0
- package/dist/security/profiles.js +104 -0
- package/dist/security/untrusted.js +23 -0
- package/dist/shared/errorContract.js +154 -0
- package/dist/shared/errors.js +21 -0
- package/dist/shared/pagination.js +135 -0
- package/dist/shared/schemaCoercion.js +173 -0
- package/dist/shared/stableJson.js +27 -0
- package/dist/shared/types.js +1 -0
- package/dist/shared/version.js +82 -0
- package/dist/startup/cliArgs.js +103 -0
- package/dist/startup/configReload.js +57 -0
- package/dist/startup/hooks.js +89 -0
- package/dist/startup/lifecycle.js +22 -0
- package/dist/startup/portConfig.js +127 -0
- package/dist/startup/reconcile.js +81 -0
- package/dist/startup/registrars.js +59 -0
- package/dist/startup/serverMode.js +19 -0
- package/dist/startup/startupEnv.js +142 -0
- package/dist/tools/animation.js +88 -0
- package/dist/tools/asset.js +58 -0
- package/dist/tools/assetWrite.js +16 -0
- package/dist/tools/audio.js +40 -0
- package/dist/tools/classdb.js +43 -0
- package/dist/tools/collision.js +23 -0
- package/dist/tools/debug.js +46 -0
- package/dist/tools/diff.js +18 -0
- package/dist/tools/editor.js +222 -0
- package/dist/tools/file.js +38 -0
- package/dist/tools/folder.js +27 -0
- package/dist/tools/inputMap.js +54 -0
- package/dist/tools/layerNames.js +29 -0
- package/dist/tools/lsp.js +524 -0
- package/dist/tools/navigation.js +24 -0
- package/dist/tools/node.js +145 -0
- package/dist/tools/nodeManagement.js +82 -0
- package/dist/tools/particles.js +83 -0
- package/dist/tools/path.js +30 -0
- package/dist/tools/playtest.js +99 -0
- package/dist/tools/procedural.js +101 -0
- package/dist/tools/resource.js +47 -0
- package/dist/tools/runtime.js +338 -0
- package/dist/tools/save.js +64 -0
- package/dist/tools/scene.js +102 -0
- package/dist/tools/sceneInheritance.js +19 -0
- package/dist/tools/sceneQuery.js +38 -0
- package/dist/tools/script.js +75 -0
- package/dist/tools/signals.js +53 -0
- package/dist/tools/sound.js +31 -0
- package/dist/tools/spatial.js +41 -0
- package/dist/tools/spriteframes.js +94 -0
- package/dist/tools/texture.js +35 -0
- package/dist/tools/theme.js +35 -0
- package/dist/tools/threeD.js +116 -0
- package/dist/tools/tilemap.js +51 -0
- package/dist/tools/tileset.js +226 -0
- package/dist/transport/authHandshake.js +43 -0
- package/dist/transport/bridge.js +225 -0
- package/dist/transport/channel.js +352 -0
- package/dist/transport/heartbeat.js +54 -0
- package/dist/transport/runtimeConnection.js +239 -0
- package/dist/transport/tokenPath.js +98 -0
- package/package.json +97 -4
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { registerTools } from "../registration/toolRegistry.js";
|
|
3
|
+
import { jsonCoerce, coercedBoolean } from "../shared/schemaCoercion.js";
|
|
4
|
+
import { toolErrorFromException, toolErrorFromPayload } from "../shared/errorContract.js";
|
|
5
|
+
import { stableStringify } from "../shared/stableJson.js";
|
|
6
|
+
import { PROJECT_FILE_PATH } from "../security/pathGuard.js";
|
|
7
|
+
import { buildScreenshotResult } from "../registration/screenshotResponse.js";
|
|
8
|
+
import { PAGE_FIELD } from "../shared/pagination.js";
|
|
9
|
+
// ── Tool definitions ─────────────────────────────────────────────────
|
|
10
|
+
export const editorTools = [
|
|
11
|
+
{
|
|
12
|
+
name: "editor_save_scene",
|
|
13
|
+
method: "editor.save_scene",
|
|
14
|
+
description: "Save the current edited scene. Optional file_path triggers save-as.",
|
|
15
|
+
inputSchema: { file_path: z.string().optional() },
|
|
16
|
+
annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: false, destructiveHint: false },
|
|
17
|
+
pathParams: [PROJECT_FILE_PATH],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: "editor_screenshot",
|
|
21
|
+
method: "editor.screenshot",
|
|
22
|
+
description: "Capture the editor viewport (NOT the running game — use runtime_screenshot for that). Pass node_path to focus one node. image_response_mode 'disk' saves the PNG and returns only its path.",
|
|
23
|
+
inputSchema: {
|
|
24
|
+
save_path: z
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Destination .png used by image_response_mode disk/both (res:// or user://screenshots/); auto-named under user://screenshots/ when omitted."),
|
|
28
|
+
node_path: z.string().optional().describe("Focus + capture a specific node instead of the full viewport"),
|
|
29
|
+
image_response_mode: z
|
|
30
|
+
.enum(["inline", "disk", "both"])
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("How to return the capture: 'inline' (default) embeds the PNG; 'disk' persists it and returns only the path — use for very large captures or to conserve context tokens; 'both' does both. Files written to disk are always full resolution, regardless of image_detail."),
|
|
33
|
+
image_detail: z
|
|
34
|
+
.enum(["full", "mid", "low"])
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Resolution of the returned inline image only. full = native; mid ≈ 1024 px long edge; low ≈ 512 px (gross layout/motion only — not for reading text). Does not affect files written to disk."),
|
|
37
|
+
force_foreground_editor: z
|
|
38
|
+
.boolean()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("If true, un-minimize + raise/focus the editor window before capturing (default false). Set it when driving from a terminal and editor_screenshot reports EDITOR_VIEWPORT_UNAVAILABLE; leave false in interactive use so your window isn't raised."),
|
|
41
|
+
},
|
|
42
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
43
|
+
successHint: "For running game visuals use runtime_screenshot. Pass node_path for focused capture.",
|
|
44
|
+
// save_path accepts res:// OR user://screenshots/ (matches editor_commands.gd).
|
|
45
|
+
pathParams: [{ param: "save_path", prefixes: ["res://", "user://screenshots/"] }],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
// Deliberately detailed description: agents must know this handles ALL file
|
|
49
|
+
// types (not just scripts) and when to call it (after external file creation).
|
|
50
|
+
name: "editor_refresh",
|
|
51
|
+
method: "editor.refresh",
|
|
52
|
+
description: "Refresh the editor's view of the filesystem — picks up new, changed, or deleted files (images, scenes, scripts, resources) and reloads open scripts. Call after creating files externally (e.g. Python, Bash) or after batch edits. With file_paths, targets specific files (O(1) per file). Without, does a full project rescan + reimport.",
|
|
53
|
+
inputSchema: {
|
|
54
|
+
file_paths: z
|
|
55
|
+
.preprocess(jsonCoerce, z.array(z.string()))
|
|
56
|
+
.optional()
|
|
57
|
+
.describe("res:// paths to update; omit for full scan"),
|
|
58
|
+
},
|
|
59
|
+
annotations: { readOnlyHint: false, openWorldHint: false, destructiveHint: false },
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "scene_open",
|
|
63
|
+
method: "scene.open",
|
|
64
|
+
description: "Open a scene (.tscn / .scn) as the active edited scene. res:// only; NOT_FOUND if the file doesn't exist.",
|
|
65
|
+
inputSchema: { file_path: z.string() },
|
|
66
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
67
|
+
successHint: "View structure with scene_get_tree. Query specific nodes with scene_query.",
|
|
68
|
+
pathParams: [PROJECT_FILE_PATH],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "scene_close",
|
|
72
|
+
method: "scene.close",
|
|
73
|
+
description: "Close an open scene tab by file_path. Discards unsaved edits — save with editor_save_scene first. Auto-creates an empty scene when the last tab closes. NOT_FOUND if not open. Requires Godot 4.5+.",
|
|
74
|
+
inputSchema: { file_path: z.string() },
|
|
75
|
+
annotations: { readOnlyHint: false, openWorldHint: false, destructiveHint: true },
|
|
76
|
+
godotMinVersion: "4.5",
|
|
77
|
+
pathParams: [PROJECT_FILE_PATH],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "project_get_settings",
|
|
81
|
+
method: "project.get_settings",
|
|
82
|
+
description: "List ProjectSettings keys + values. Optional prefix filter. Keys matching /password|token|secret|key/i are dropped (MVP filter).",
|
|
83
|
+
inputSchema: { prefix: z.string().optional() },
|
|
84
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "editor_get_console",
|
|
88
|
+
method: "editor.get_console",
|
|
89
|
+
description: "Tail editor Output. source='buffer' (default): live editor console on 4.5+, game-log tail on 4.2-4.4. source='file': the game-written log (never editor output, any version). " +
|
|
90
|
+
"level_filter, since_id, text_filter (is_regex=true for regex). Carries returned/total_lines/has_more + next_id — page via since_id. " +
|
|
91
|
+
"Primary post-crash diagnostic tool — reads runtime errors even after game_stop.",
|
|
92
|
+
inputSchema: {
|
|
93
|
+
limit: z.coerce.number().optional(),
|
|
94
|
+
level_filter: z
|
|
95
|
+
.union([z.enum(["info", "warning", "error"]), z.array(z.enum(["info", "warning", "error"]))])
|
|
96
|
+
.optional()
|
|
97
|
+
.describe("Single level or array of levels to filter by"),
|
|
98
|
+
since_id: z.coerce.number().optional(),
|
|
99
|
+
source: z.enum(["buffer", "file"]).optional(),
|
|
100
|
+
text_filter: z
|
|
101
|
+
.string()
|
|
102
|
+
.optional()
|
|
103
|
+
.describe("Substring to match against message text (case-insensitive). Set is_regex=true for regex patterns."),
|
|
104
|
+
is_regex: coercedBoolean()
|
|
105
|
+
.optional()
|
|
106
|
+
.describe("Treat text_filter as a regex pattern instead of a plain substring (default false)."),
|
|
107
|
+
clear_buffer: coercedBoolean()
|
|
108
|
+
.optional()
|
|
109
|
+
.describe("Clear the log buffer before reading. Use when stale errors persist after successful script recompilation."),
|
|
110
|
+
},
|
|
111
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
112
|
+
successHint: "For runtime crash logs use debugger_get_log. Use since_id for incremental reads.",
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "editor_wait_for_idle",
|
|
116
|
+
method: "editor.wait_for_idle",
|
|
117
|
+
description: "Poll EditorFileSystem.is_scanning() until idle or timeout_ms (default 10s, cap 30s). Use after asset.import, editor.refresh, or file mutations.",
|
|
118
|
+
inputSchema: {
|
|
119
|
+
timeout_ms: z.coerce.number().optional(),
|
|
120
|
+
},
|
|
121
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: "project_set_setting",
|
|
125
|
+
method: "project.set_setting",
|
|
126
|
+
description: "Write a ProjectSettings key and persist via ProjectSettings.save. Refuses mcp_toolkit/*, mcp/*, and editor/* prefixes. Returns previous_value. Update (no status).",
|
|
127
|
+
inputSchema: {
|
|
128
|
+
setting: z.string().describe("ProjectSettings key (e.g. 'application/config/name')"),
|
|
129
|
+
value: z.unknown(),
|
|
130
|
+
},
|
|
131
|
+
annotations: { readOnlyHint: false, openWorldHint: false, destructiveHint: false },
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
// ── Custom handlers ──────────────────────────────────────────────────
|
|
135
|
+
/**
|
|
136
|
+
* Summary-first handler for editor_get_console — prefixes a line count
|
|
137
|
+
* so the model sees the headline before the (potentially large) output.
|
|
138
|
+
*/
|
|
139
|
+
async function consoleSummaryHandler(bridge, method, input) {
|
|
140
|
+
try {
|
|
141
|
+
// Normalize level_filter: wrap single string in an array for the plugin.
|
|
142
|
+
const parsed = input;
|
|
143
|
+
if (typeof parsed?.level_filter === "string") {
|
|
144
|
+
parsed.level_filter = [parsed.level_filter];
|
|
145
|
+
}
|
|
146
|
+
const result = await bridge.call(method, parsed);
|
|
147
|
+
const err = toolErrorFromPayload(result);
|
|
148
|
+
if (err)
|
|
149
|
+
return err;
|
|
150
|
+
// The toolkit is the sole envelope author (REFLECT); read its paged fields
|
|
151
|
+
// through PAGE_FIELD so a rename lands in one place, not scattered literals.
|
|
152
|
+
const obj = result;
|
|
153
|
+
const returned = typeof obj[PAGE_FIELD.returned] === "number" ? obj[PAGE_FIELD.returned] : 0;
|
|
154
|
+
// total_lines: the pre-cap line count (full number of lines before the tail slice).
|
|
155
|
+
const total = typeof obj[PAGE_FIELD.totalLines] === "number" ? obj[PAGE_FIELD.totalLines] : returned;
|
|
156
|
+
const summary = `${returned} line${returned !== 1 ? "s" : ""} (of ${total} total)`;
|
|
157
|
+
const text = stableStringify({ _summary: summary, ...obj });
|
|
158
|
+
return { content: [{ type: "text", text }] };
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
return toolErrorFromException(e);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Handler for the editor screenshot tool. The inline/both capture returns an
|
|
166
|
+
* image block plus metadata; a disk-mode capture persists the PNG toolkit-side
|
|
167
|
+
* and returns just its file path (no image bytes) — that lean payload carries a
|
|
168
|
+
* `path` with no `image_base64`, so it takes the disk branch before the
|
|
169
|
+
* empty-content guard rather than being mistaken for a failed capture.
|
|
170
|
+
*/
|
|
171
|
+
async function screenshotHandler(bridge, method, input) {
|
|
172
|
+
let result;
|
|
173
|
+
try {
|
|
174
|
+
result = (await bridge.call(method, input ?? {}));
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
return toolErrorFromException(err);
|
|
178
|
+
}
|
|
179
|
+
const payloadErr = toolErrorFromPayload(result);
|
|
180
|
+
if (payloadErr)
|
|
181
|
+
return payloadErr;
|
|
182
|
+
// Disk-mode capture: PNG persisted, only the path returned. A saved path with
|
|
183
|
+
// no image bytes is a success, not the empty-content failure below.
|
|
184
|
+
if (result?.path && !result.image_base64) {
|
|
185
|
+
return buildScreenshotResult(undefined, result.mime_type, {
|
|
186
|
+
width: result.width,
|
|
187
|
+
height: result.height,
|
|
188
|
+
bytes: result.bytes,
|
|
189
|
+
path: result.path,
|
|
190
|
+
remediation: result.remediation,
|
|
191
|
+
hint: result.hint,
|
|
192
|
+
image_detail: result.image_detail,
|
|
193
|
+
returned: result.returned,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
if (!result?.image_base64) {
|
|
197
|
+
return toolErrorFromPayload({
|
|
198
|
+
success: false,
|
|
199
|
+
code: "EMPTY_CONTENT",
|
|
200
|
+
error: "screenshot returned no image bytes — node may lack visual content. Use editor_screenshot for full viewport.",
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return buildScreenshotResult(result.image_base64, result.mime_type, {
|
|
204
|
+
width: result.width,
|
|
205
|
+
height: result.height,
|
|
206
|
+
bytes: result.bytes,
|
|
207
|
+
path: result.path,
|
|
208
|
+
remediation: result.remediation,
|
|
209
|
+
hint: result.hint,
|
|
210
|
+
image_detail: result.image_detail,
|
|
211
|
+
returned: result.returned,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
// ── Registration ─────────────────────────────────────────────────────
|
|
215
|
+
export function register(server, bridge, allowedTools) {
|
|
216
|
+
const handlers = new Map();
|
|
217
|
+
handlers.set("editor_get_console", (input) => consoleSummaryHandler(bridge, "editor.get_console", input));
|
|
218
|
+
// Screenshot handler returns image+text multi-content; cast to ToolTextResult
|
|
219
|
+
// since the MCP SDK accepts any content type at runtime.
|
|
220
|
+
handlers.set("editor_screenshot", (input) => screenshotHandler(bridge, "editor.screenshot", input));
|
|
221
|
+
registerTools(server, bridge, editorTools, allowedTools, { handlers });
|
|
222
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { registerTools } from "../registration/toolRegistry.js";
|
|
3
|
+
import { callAndWrap } from "../registration/toolDispatch.js";
|
|
4
|
+
import { PROJECT_FILE_PATH } from "../security/pathGuard.js";
|
|
5
|
+
export const fileTools = [
|
|
6
|
+
{
|
|
7
|
+
name: "file_delete",
|
|
8
|
+
method: "file.delete",
|
|
9
|
+
description: "Delete any file under res:// and its .import companion. Auto-closes .tscn/.scn editor tabs on 4.5+ (tab_closed:true). Use for assets not covered by scene/script/resource.delete.",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
file_path: z.string(),
|
|
12
|
+
},
|
|
13
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
14
|
+
pathParams: [PROJECT_FILE_PATH],
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
// ── Conditional hint for file_delete ─────────────────────────────────
|
|
18
|
+
/** Suggest the specialized delete tool when file_delete is used on a typed resource. */
|
|
19
|
+
function fileDeleteHint(filePath) {
|
|
20
|
+
if (/\.(?:tscn|scn)$/i.test(filePath)) {
|
|
21
|
+
return "For .tscn/.scn files, prefer scene_delete — it handles open-tab cleanup.";
|
|
22
|
+
}
|
|
23
|
+
if (/\.(?:gd|gdshader|gdshaderinc)$/i.test(filePath)) {
|
|
24
|
+
return "For scripts, prefer script_delete.";
|
|
25
|
+
}
|
|
26
|
+
if (/\.(?:tres|res)$/i.test(filePath)) {
|
|
27
|
+
return "For resources, prefer resource_delete.";
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
export function register(server, bridge, allowedTools) {
|
|
32
|
+
const handlers = new Map();
|
|
33
|
+
handlers.set("file_delete", async (input, signal) => {
|
|
34
|
+
const filePath = typeof input.file_path === "string" ? input.file_path : "";
|
|
35
|
+
return callAndWrap(bridge, "file.delete", input, { signal, successHint: fileDeleteHint(filePath) });
|
|
36
|
+
});
|
|
37
|
+
registerTools(server, bridge, fileTools, allowedTools, { handlers });
|
|
38
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { registerTools } from "../registration/toolRegistry.js";
|
|
3
|
+
import { coercedBoolean } from "../shared/schemaCoercion.js";
|
|
4
|
+
export const folderTools = [
|
|
5
|
+
{
|
|
6
|
+
name: "folder_create",
|
|
7
|
+
method: "folder.create",
|
|
8
|
+
description: "Create directory at res:// path (recursive — parents auto-created). Idempotent: status created on fresh, returned if pre-existing.",
|
|
9
|
+
inputSchema: { path: z.string() },
|
|
10
|
+
annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: false, destructiveHint: false },
|
|
11
|
+
pathParams: [{ param: "path", guard: "project" }],
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: "folder_delete",
|
|
15
|
+
method: "folder.delete",
|
|
16
|
+
description: "Delete directory. recursive:false(default) requires empty. On 4.5+ closes one open scene tab; multiple in stale_tabs - use scene_close. Refuses project root, addons, open scripts (PATH_IN_USE).",
|
|
17
|
+
inputSchema: {
|
|
18
|
+
path: z.string(),
|
|
19
|
+
recursive: coercedBoolean().optional(),
|
|
20
|
+
},
|
|
21
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
22
|
+
pathParams: [{ param: "path", guard: "project" }],
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
export function register(server, bridge, allowedTools) {
|
|
26
|
+
registerTools(server, bridge, folderTools, allowedTools);
|
|
27
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { registerTools } from "../registration/toolRegistry.js";
|
|
3
|
+
// input_map tools write to ProjectSettings (input/* keys). Ungated since
|
|
4
|
+
// project_set_setting can already write the same keys — these structured
|
|
5
|
+
// tools are safer than raw key-value writes.
|
|
6
|
+
export const inputMapTools = [
|
|
7
|
+
{
|
|
8
|
+
name: "input_map_action",
|
|
9
|
+
method: "input_map.action",
|
|
10
|
+
description: "Add or remove an InputMap action. " +
|
|
11
|
+
"action: 'add' or 'remove' (the operation). " +
|
|
12
|
+
"name: the input map name (e.g. 'jump', 'move_left'). " +
|
|
13
|
+
"action='add' is idempotent with optional deadzone.",
|
|
14
|
+
inputSchema: {
|
|
15
|
+
action: z.enum(["add", "remove"]),
|
|
16
|
+
name: z.string(),
|
|
17
|
+
deadzone: z.coerce.number().optional(),
|
|
18
|
+
},
|
|
19
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false, destructiveHint: false },
|
|
20
|
+
operationParam: "action",
|
|
21
|
+
successHint: "Bind events with input_map_event after creating the action.",
|
|
22
|
+
},
|
|
23
|
+
// Deliberately detailed description: all 4 event types (key, mouse_button,
|
|
24
|
+
// joypad_button, joypad_motion) need inline examples to avoid agent guesswork.
|
|
25
|
+
{
|
|
26
|
+
name: "input_map_event",
|
|
27
|
+
method: "input_map.event",
|
|
28
|
+
description: "Bind/unbind an input event to an action. " +
|
|
29
|
+
"action: 'bind' or 'unbind' (the operation). " +
|
|
30
|
+
"event: object — {type:'key', keycode:'Space'}, {type:'mouse_button', button_index:1}, " +
|
|
31
|
+
"{type:'joypad_button', button_index:0}, {type:'joypad_motion', axis:0, axis_value:1.0}. " +
|
|
32
|
+
"action='bind' is idempotent.",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
action: z.enum(["bind", "unbind"]),
|
|
35
|
+
name: z.string(),
|
|
36
|
+
event: z.preprocess((val) => {
|
|
37
|
+
if (typeof val === "string") {
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(val);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return val;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return val;
|
|
46
|
+
}, z.record(z.string(), z.unknown())),
|
|
47
|
+
},
|
|
48
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false, destructiveHint: false },
|
|
49
|
+
operationParam: "action",
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
export function register(server, bridge, allowedTools) {
|
|
53
|
+
registerTools(server, bridge, inputMapTools, allowedTools);
|
|
54
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { registerTools } from "../registration/toolRegistry.js";
|
|
3
|
+
import { jsonCoerce } from "../shared/schemaCoercion.js";
|
|
4
|
+
const CATEGORY_ENUM = z.enum(["2d_physics", "2d_render", "3d_physics", "3d_render"]);
|
|
5
|
+
export const layerNameTools = [
|
|
6
|
+
{
|
|
7
|
+
name: "layer_names_set",
|
|
8
|
+
method: "project.set_layer_names",
|
|
9
|
+
description: "Set physics/render layer names. category: 2d_physics|2d_render|3d_physics|3d_render. layers: {1:'Ground', 2:'Player', …} (keys 1-32).",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
category: CATEGORY_ENUM,
|
|
12
|
+
layers: z.preprocess(jsonCoerce, z.record(z.string(), z.string())).describe("Layer number (1-32) to name"),
|
|
13
|
+
},
|
|
14
|
+
annotations: { readOnlyHint: false, openWorldHint: false, destructiveHint: false },
|
|
15
|
+
successHint: "Set layer masks on nodes via node_set_property with LayerMask type tag.",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "layer_names_get",
|
|
19
|
+
method: "project.get_layer_names",
|
|
20
|
+
description: "Read named physics/render layers. Returns only layers with non-empty names.",
|
|
21
|
+
inputSchema: {
|
|
22
|
+
category: CATEGORY_ENUM,
|
|
23
|
+
},
|
|
24
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
export function register(server, bridge, allowedTools) {
|
|
28
|
+
registerTools(server, bridge, layerNameTools, allowedTools);
|
|
29
|
+
}
|