初次提交
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
# Godot MCP Pro - AI Assistant Instructions
|
||||
|
||||
You have access to the Godot MCP Pro toolset for building and testing Godot games through the editor. Follow these rules carefully.
|
||||
|
||||
## Critical: Editor vs Runtime Tools
|
||||
|
||||
Tools are split into two categories. **Using a runtime tool without starting the game will always fail.**
|
||||
|
||||
### Editor Tools (always available)
|
||||
These work on the currently open scene in the Godot editor:
|
||||
- **Scene**: `get_scene_tree`, `create_scene`, `open_scene`, `save_scene`, `delete_scene`, `add_scene_instance`, `get_scene_file_content`, `get_scene_exports`
|
||||
- **Nodes**: `add_node`, `delete_node`, `duplicate_node`, `move_node`, `rename_node`, `update_property`, `get_node_properties`, `add_resource`, `set_anchor_preset`, `connect_signal`, `disconnect_signal`, `get_node_groups`, `set_node_groups`, `find_nodes_in_group`
|
||||
- **Scripts**: `create_script`, `read_script`, `edit_script`, `validate_script`, `attach_script`, `get_open_scripts`, `list_scripts`
|
||||
- **Project**: `get_project_info`, `get_project_settings`, `set_project_setting`, `get_project_statistics`, `get_filesystem_tree`, `get_input_actions`, `set_input_action`
|
||||
- **Editor**: `execute_editor_script`, `get_editor_errors`, `get_output_log`, `get_editor_screenshot`, `clear_output`, `reload_plugin`, `reload_project`
|
||||
- **Resources**: `create_resource`, `read_resource`, `edit_resource`, `get_resource_preview`
|
||||
- **Batch**: `batch_add_nodes`, `batch_set_property`, `find_nodes_by_type`, `find_signal_connections`, `find_node_references`, `get_scene_dependencies`, `cross_scene_set_property`
|
||||
- **3D**: `add_mesh_instance`, `setup_environment`, `setup_lighting`, `setup_camera_3d`, `setup_collision`, `setup_physics_body`, `set_material_3d`, `add_raycast`, `add_gridmap`
|
||||
- **Animation**: `create_animation`, `add_animation_track`, `set_animation_keyframe`, `list_animations`, `get_animation_info`, `remove_animation`
|
||||
- **Animation Tree**: `create_animation_tree`, `get_animation_tree_structure`, `add_state_machine_state`, `add_state_machine_transition`, `remove_state_machine_state`, `remove_state_machine_transition`, `set_blend_tree_node`, `set_tree_parameter`
|
||||
- **Audio**: `add_audio_player`, `add_audio_bus`, `add_audio_bus_effect`, `set_audio_bus`, `get_audio_bus_layout`, `get_audio_info`
|
||||
- **Navigation**: `setup_navigation_region`, `setup_navigation_agent`, `bake_navigation_mesh`, `set_navigation_layers`, `get_navigation_info`
|
||||
- **Particles**: `create_particles`, `set_particle_material`, `set_particle_color_gradient`, `apply_particle_preset`, `get_particle_info`
|
||||
- **Physics**: `get_physics_layers`, `set_physics_layers`, `get_collision_info`
|
||||
- **Shader**: `create_shader`, `read_shader`, `edit_shader`, `assign_shader_material`, `get_shader_params`, `set_shader_param`
|
||||
- **Theme**: `create_theme`, `get_theme_info`, `set_theme_color`, `set_theme_font_size`, `set_theme_constant`, `set_theme_stylebox`
|
||||
- **Tilemap**: `tilemap_get_info`, `tilemap_set_cell`, `tilemap_get_cell`, `tilemap_fill_rect`, `tilemap_clear`, `tilemap_get_used_cells`
|
||||
- **Export**: `list_export_presets`, `get_export_info`, `export_project`
|
||||
- **Analysis**: `analyze_scene_complexity`, `analyze_signal_flow`, `detect_circular_dependencies`, `find_unused_resources`, `get_performance_monitors`, `search_files`, `search_in_files`, `find_script_references`
|
||||
- **Profiling**: `get_editor_performance`
|
||||
|
||||
### Runtime Tools (require `play_scene` first)
|
||||
You MUST call `play_scene` before using any of these. They interact with the running game:
|
||||
- **Game State**: `get_game_scene_tree`, `get_game_node_properties`, `set_game_node_property`, `execute_game_script`, `get_game_screenshot`, `get_autoload`, `find_nodes_by_script`
|
||||
- **Input Simulation**: `simulate_key`, `simulate_mouse_click`, `simulate_mouse_move`, `simulate_action`, `simulate_sequence`
|
||||
- **Capture/Recording**: `capture_frames`, `record_frames`, `monitor_properties`, `start_recording`, `stop_recording`, `replay_recording`, `batch_get_properties`
|
||||
- **UI Interaction**: `find_ui_elements`, `click_button_by_text`, `wait_for_node`, `find_nearby_nodes`, `navigate_to`, `move_to`
|
||||
- **Testing**: `run_test_scenario`, `assert_node_state`, `assert_screen_text`, `run_stress_test`, `get_test_report`
|
||||
- **Screenshots**: `get_game_screenshot`, `compare_screenshots`
|
||||
- **Control**: `play_scene`, `stop_scene`
|
||||
|
||||
## Workflow Patterns
|
||||
|
||||
### Building a scene from scratch
|
||||
1. `create_scene` or `open_scene`
|
||||
2. Use `add_node` or `batch_add_nodes` to add nodes
|
||||
3. `create_script` + `attach_script` for behavior
|
||||
4. `save_scene`
|
||||
|
||||
### Testing gameplay
|
||||
1. Build scene with editor tools (above)
|
||||
2. `play_scene` to start the game
|
||||
3. Use `simulate_key`/`simulate_mouse_click` for input
|
||||
4. `get_game_screenshot` or `capture_frames` to observe results
|
||||
5. `stop_scene` when done
|
||||
|
||||
### Inspecting a project
|
||||
1. `get_project_info` for overview
|
||||
2. `get_scene_tree` for current scene structure
|
||||
3. `read_script` to read code
|
||||
4. `get_node_properties` for specific node details
|
||||
|
||||
### Migrating code properties to inspector
|
||||
When a script hardcodes visual properties (colors, sizes, positions, theme overrides) that should be in the inspector:
|
||||
1. `read_script` to find hardcoded property assignments (e.g. `modulate = Color(...)`, `add_theme_color_override(...)`)
|
||||
2. `get_node_properties` to see current inspector values
|
||||
3. `update_property` to set the same values as node properties in the inspector
|
||||
4. `edit_script` to remove the hardcoded lines from the script
|
||||
5. `save_scene` to persist the inspector changes
|
||||
6. `validate_script` to verify the script still works
|
||||
|
||||
## Formatting Rules
|
||||
|
||||
### execute_editor_script
|
||||
The `code` parameter must be valid GDScript. Use `_mcp_print(value)` to return output.
|
||||
|
||||
```
|
||||
# Correct
|
||||
_mcp_print("hello")
|
||||
|
||||
# Correct - multi-line
|
||||
var nodes = []
|
||||
for child in EditorInterface.get_edited_scene_root().get_children():
|
||||
nodes.append(child.name)
|
||||
_mcp_print(str(nodes))
|
||||
```
|
||||
|
||||
### execute_game_script
|
||||
Same as above but runs inside the running game. Additional rules:
|
||||
- No nested functions (`func` inside `func` is invalid GDScript)
|
||||
- Use `.get("property")` instead of `.property` for safe access
|
||||
- Runs in a temporary node — use `get_tree()` to access the scene tree
|
||||
|
||||
### batch_add_nodes
|
||||
Pass an array of node definitions. Nodes are processed in order, so earlier nodes can be parents for later ones:
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{"type": "Node2D", "name": "Container", "parent_path": "."},
|
||||
{"type": "Sprite2D", "name": "Icon", "parent_path": "Container"},
|
||||
{"type": "Label", "name": "Title", "parent_path": "Container", "properties": {"text": "Hello"}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Prefer inspector properties over code** — When changing visual properties (colors, sizes, theme overrides, transforms, etc.), use `update_property` to set them directly on the node. This keeps values visible in the Godot inspector and easy to tweak. Only use GDScript when the property isn't available in the inspector or needs to be dynamic at runtime.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
1. **Never edit project.godot directly** — Use `set_project_setting` instead. The Godot editor overwrites the file.
|
||||
2. **GDScript type inference** — Use explicit type annotations in for-loops: `for item: String in array` instead of `for item in array`.
|
||||
3. **Reload after script changes** — After `create_script`, call `reload_project` if the script doesn't take effect.
|
||||
4. **Property values as strings** — Properties like position accept string format: `"Vector2(100, 200)"`, `"Color(1, 0, 0, 1)"`.
|
||||
5. **simulate_key duration** — Use short durations (0.3-0.5s) for precise movement. Integer seconds (1, 2, 3) cause overshooting.
|
||||
6. **compare_screenshots** — Pass file paths (`user://screenshot.png`), not base64 data.
|
||||
|
||||
## CLI Mode (Alternative to MCP Tools)
|
||||
|
||||
If MCP tools are unavailable or you have a terminal/bash tool, you can control Godot via the CLI.
|
||||
The CLI requires the server to be built first (`node build/setup.js install` in the server directory).
|
||||
|
||||
```bash
|
||||
# Discover available command groups
|
||||
node /path/to/server/build/cli.js --help
|
||||
|
||||
# Discover commands in a group
|
||||
node /path/to/server/build/cli.js scene --help
|
||||
|
||||
# Discover options for a specific command
|
||||
node /path/to/server/build/cli.js node add --help
|
||||
|
||||
# Execute commands
|
||||
node /path/to/server/build/cli.js project info
|
||||
node /path/to/server/build/cli.js scene tree
|
||||
node /path/to/server/build/cli.js node add --type CharacterBody3D --name Player --parent /root/Main
|
||||
node /path/to/server/build/cli.js script read --path res://player.gd
|
||||
node /path/to/server/build/cli.js scene play
|
||||
node /path/to/server/build/cli.js input key --key W --duration 0.5
|
||||
node /path/to/server/build/cli.js runtime tree
|
||||
```
|
||||
|
||||
**Command groups**: project, scene, node, script, editor, input, runtime
|
||||
|
||||
Always start by running `--help` to discover available commands. Use the CLI when MCP tools are not loaded or when you need to reduce context usage.
|
||||
@@ -0,0 +1,699 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to Godot MCP Pro will be documented in this file.
|
||||
|
||||
---
|
||||
|
||||
## v1.15.1 — 2026-07-19
|
||||
|
||||
**Patch** — 15 fixes from an external user's full-toolset audit (all 174 tools tested against a live editor). Huge thanks to the reporter.
|
||||
|
||||
### Fixed — Critical
|
||||
- **`set_particle_color_gradient` infinite loop / editor hang**: clearing a fresh `Gradient` point-by-point never terminates because `Gradient.remove_point` refuses to drop below 2 points. Gradients are now built by assigning `offsets` / `colors` wholesale. The same root cause also produced a spurious opaque-black stop at offset 0 in every `apply_particle_preset` color ramp — both fixed via the same change.
|
||||
- **`connect_signal` connections were never saved to the `.tscn`**: `Object.connect()` was called without `CONNECT_PERSIST`, so `PackedScene.pack()` dropped the connection on save. Connections are now persistent; new optional `deferred` / `one_shot` parameters, and the response echoes `flags` / `persistent`.
|
||||
- **`update_property` destroyed Resource-typed properties**: assigning e.g. `texture` by `"res://..."` path passed the raw String through, which the engine coerced to `null` — silently wiping the existing value. `PropertyParser` now loads `res://` / `uid://` paths into Resources (also fixes `batch_add_nodes` properties), and `update_property` fails loudly when a string cannot be resolved instead of committing a `null`.
|
||||
|
||||
### Fixed — High
|
||||
- **`create_theme` returned `{}` and never wrote the file**: a `!= null` check on a `Dictionary` guard made the guard branch unconditional. Now uses `is_empty()` like every other call site. Also creates parent directories.
|
||||
- **`get_performance_monitors` reported the editor process's metrics as the game's**: `Performance` is per-process. The tool now routes through the game IPC channel (requires a playing scene) and returns `"process": "game"`; use `get_editor_performance` for editor metrics.
|
||||
- **`get_test_report` counted passing assertions as failures**: non-assertion steps (input/wait/screenshot) were scored as failed, and game replies were stored still double-wrapped so their `passed` key was never found. Only assertion results are stored now, and the envelope is unwrapped defensively. Empty reports return `no_results: true` instead of a misleading `all_passed: false`.
|
||||
- **`run_test_scenario` reported `all_passed: false` on green runs**: same double-wrapped envelope, unwrapped one level too few in `_execute_assert_step`. Assertion results now surface `passed` at the top level with a consistent shape.
|
||||
|
||||
### Fixed — Medium
|
||||
- **`get_scene_tree` returned editor-internal absolute paths** (`/root/@EditorNode@.../...`): paths are now scene-relative (root = `"."`), directly usable as `node_path` input for other tools, and ~10× smaller.
|
||||
- **`analyze_signal_flow` dumped editor-internal connections** (~8k tokens of dock bookkeeping): now filters to persistent connections targeting nodes inside the edited scene.
|
||||
- **`find_unused_resources` ignored `uid://` references**: `preload("uid://…")` and `uid=` references are now resolved back to their `res://` paths (self-referencing file-header uids excluded). References held via `ProjectSettings` defaults (main scene, audio bus layout, icon, autoloads) are also seeded, so `default_bus_layout.tres` and friends are no longer reported deletable.
|
||||
- **`get_input_actions(include_builtin: false)` leaked 16 editor actions** (`spatial_editor/*`): actions not declared in the project's `ProjectSettings` are now excluded.
|
||||
- **`create_resource` failed on missing parent directories**: directories are created recursively; the error message now includes the path.
|
||||
- **`run_test_scenario` `keycode` input steps never released the key**, corrupting later assertions: keycode steps now auto-release like `action` steps (disable with `auto_release: false`).
|
||||
|
||||
### Fixed — Minor
|
||||
- **`set_particle_material`**: emission sub-parameters (`emission_sphere_radius`, box extents, ring radii/height) are now listed in `changes[]`.
|
||||
- `run_test_scenario` screen-text assertions no longer lose their result type to a key collision (`assert_type` field).
|
||||
|
||||
---
|
||||
|
||||
## v1.14.1 — 2026-05-24
|
||||
|
||||
**Patch** — `assert_node_state` regression fix
|
||||
|
||||
### Fixed
|
||||
- **`assert_node_state` "Unknown command" regression**: The game-side handler in `mcp_game_inspector_service.gd` had been removed during the v1.7.0 refactor (`4ea3989`, 2026-03-29) that introduced `batch_add_nodes` / `watch_signals` / `setup_control`. The TypeScript server and editor-side GDScript still routed the call, but the runtime match statement no longer recognized it — so `assert_node_state` (and any `type:"assert"` step inside `run_test_scenario`) returned `Unknown command: assert_node_state` on every call from v1.7.0 through v1.14.0. Restored the handler with all 8 operators (`eq`, `neq`, `gt`, `lt`, `gte`, `lte`, `contains`, `type_is`) and sub-property access via `get_indexed()` (e.g. `position:y`). Reported by **Z_runner [CRWN]** on Discord.
|
||||
|
||||
---
|
||||
|
||||
## v1.14.0 — 2026-05-18
|
||||
|
||||
**Feature / Safety** — File-conflict safety overhaul (community contribution)
|
||||
|
||||
This release is a coordinated overhaul of how the addon interacts with editor-owned resources. It prevents the "external change" dialog and silent in-memory/disk divergence that could occur when MCP commands wrote scenes, scripts, shaders, or resources while Godot still had them open. Contributed by **[@aallnneess](https://github.com/aallnneess)** (PR #31), with the design and patch reviewed and tested locally with GitHub Copilot using GPT-5.5 xhigh. Reported and validated against the previous v1.13.x behavior.
|
||||
|
||||
### Added — safety primitives in `base_command.gd`
|
||||
- `guard_offline_scene_save(path)`: blocks `ResourceSaver.save(...)` to a `.tscn`/`.scn` path when that scene is currently open in the editor. Returns a structured conflict error (JSON-RPC code `-32009`) including the path, open-scenes list, and a recovery suggestion.
|
||||
- `guard_text_resource_write(path, force)`: blocks writes to a script/shader file that is currently open in Godot's script editor (or, for shaders, currently loaded/cached in `ResourceLoader`). Override with `force=true`.
|
||||
- `add_child_with_undo(...)` / `set_property_with_undo(...)`: register live scene mutations through `EditorUndoRedoManager` with correct `add_do_reference` / `add_undo_reference` retention for `Resource` values so they survive GC across the undo history.
|
||||
- `get_open_scene_paths()` / `is_scene_path_open()` / `is_active_scene_path()` / `is_text_resource_open_in_script_editor()`: shared checks so per-command code never re-implements the same logic.
|
||||
- `normalize_project_path()`: consistent comparison key for `res://`, project-relative, and absolute paths.
|
||||
|
||||
### Changed — scene saves go through `EditorInterface`
|
||||
- `save_scene`: when the target path matches the active edited scene, calls `EditorInterface.save_scene()`; when the target differs or the active scene has no path yet, calls `EditorInterface.save_scene_as(path)`. Refuses to save an inactive open scene tab. No more silent `ResourceSaver.save` to an open path.
|
||||
- `create_scene` / `create_theme` / `edit_resource`: all guarded against accidentally targeting an open scene path.
|
||||
|
||||
### Changed — broad cross-scene edits are opt-in
|
||||
- **`cross_scene_set_property` defaults to `dry_run=true`** (breaking change). Real writes now require `dry_run=false` **and** `force=true`. The response includes a per-scene `mode` field (`dry_run` / `offline_saved` / `live_open_scene`) and a `skipped_open_scenes` list so callers can see exactly what happened.
|
||||
- The active open scene is live-edited via `EditorUndoRedoManager` instead of being offline-overwritten, so changes are visible in the editor and undoable.
|
||||
- Inactive open scenes are skipped and reported rather than silently overwritten.
|
||||
|
||||
### Changed — live scene mutations participate in UndoRedo
|
||||
- `batch_add_nodes`, `batch_set_property`: routed through the shared UndoRedo helpers.
|
||||
- `node_commands`: node creation, `set_anchor_preset` (computed on a duplicate Control before applying), signal connect/disconnect, group add/remove — all undoable.
|
||||
- `animation_commands`: animation create/remove, track add, and keyframe edits. `_upsert_animation_key` / `_restore_animation_key` give round-trip undo for keyframe edits (including the previously-present-key replacement case) using `is_equal_approx` time matching.
|
||||
- `animation_tree_commands`: AnimationTree create, state machine state/transition edits, blend tree node changes, tree parameter edits.
|
||||
- `tilemap_commands`: single-cell set, rect fill, and clear capture the affected cells' old state and apply via UndoRedo. Round-trip undoable.
|
||||
- `theme_commands`: color, constant, font-size, and stylebox theme overrides; `setup_control` computes target state on a duplicate Control before applying.
|
||||
- `audio_commands`, `navigation_commands`, `particle_commands`: node creation and resource assignment go through UndoRedo. Navigation bake also marks the active scene unsaved.
|
||||
- `shader_commands`: shader material assignment via `set_property_with_undo`.
|
||||
|
||||
### Fixed — open script/shader writes
|
||||
- `create_script` / `edit_script`: refuse to write when the target is open in the script editor, unless `force=true` is explicitly passed. Also restricted to `.gd` / `.cs` extensions — scene and shader paths are rejected with a clear suggestion (added in `6d8d650`).
|
||||
- `edit_script` now actually implements the 1-based inclusive `start_line` / `end_line` range replacement that the CLI had been advertising. The TypeScript `edit_script` schema and CLI `script edit` both expose the new parameters.
|
||||
- `create_shader` / `edit_shader`: same open-file guard, with `force=true` to override.
|
||||
- Shader cache refresh fixed: `_refresh_loaded_shader` uses `take_over_path()` + `emit_changed()` to update any cached/live `Shader` resource, replacing the unreliable `Shader.reload_from_file()` call. Live materials referencing the shader now pick up edits immediately.
|
||||
|
||||
### Fixed — `execute_editor_script` escape hatch
|
||||
- Submitted code is scanned for direct file/resource write APIs (`ResourceSaver.save`, `FileAccess WRITE`, `ProjectSettings.save`, `ConfigFile.save`, `DirAccess` filesystem mutations). If present, the call is refused with a structured conflict error unless `allow_unsafe_editor_io=true` is explicitly passed. Closes the obvious workaround where an AI client could route around the per-command guards by submitting raw script.
|
||||
|
||||
### Changed — server schemas
|
||||
- `create_script` / `edit_script` / `create_shader` / `edit_shader`: added optional `force` parameter and updated tool descriptions.
|
||||
- `cross_scene_set_property`: added optional `dry_run` / `force` parameters and a description that reflects the new dry-run-by-default semantics.
|
||||
- `execute_editor_script`: added optional `allow_unsafe_editor_io` parameter.
|
||||
- `cli.ts`: `script create` and `script edit` accept `--force` flag.
|
||||
|
||||
### Migration notes
|
||||
- **Breaking**: scripts/agents that previously relied on `cross_scene_set_property` writing on first call now need to add `dry_run=false force=true` to perform writes. Without those, the call returns a dry-run preview. This is intentional — silent project-wide writes were a footgun.
|
||||
- **Soft-breaking**: scripts/agents that previously overwrote open files (scenes, scripts, shaders) without checking now hit a `-32009` conflict error. The fix is to either close the file in the editor first, save through the editor (for scenes), or pass `force=true` (for scripts/shaders, when you've verified no buffer holds unsaved changes).
|
||||
- Existing wire-compatible calls that did *not* target open resources continue to work unchanged.
|
||||
|
||||
---
|
||||
|
||||
## v1.13.2 — 2026-05-13
|
||||
|
||||
**Bug Fix** — Port allocation race when multiple Claude Code sessions start at the same time
|
||||
|
||||
### Fixed
|
||||
- **Parallel-session port collision** (Discord report by CrusherEAGLE): Two Claude Code sessions starting nearly simultaneously could both pre-check port 6505 as free, both attempt to bind, the loser would get `EADDRINUSE` and give up without retrying the next port. The session that lost the race had a server that never started, so every tool call failed for the remainder of that session with no way to recover short of restart. The fallback path in `index.ts` claimed it would "retry on first command" but no such retry existed.
|
||||
- The fix replaces the racy pre-check + single bind with a proper bind-retry loop: each port in `6505–6509` is tried in turn, and `EADDRINUSE` triggers a fall-through to the next port. Only when the entire range is exhausted does `connect()` reject, with a clear error message and remediation hint.
|
||||
- Cleaned up the misleading "will retry on first command" log line in `index.ts`.
|
||||
|
||||
### Tests
|
||||
- New `tests/godot-connection.test.ts` covers: first-port allocation, sequential fall-through, **simultaneous parallel connects** (the exact regression scenario), range exhaustion, and `fixedPort=true` fail-fast behavior. 5 new tests, 62 total.
|
||||
|
||||
---
|
||||
|
||||
## v1.13.1 — 2026-05-12
|
||||
|
||||
**Bug Fix** — Silent disconnect / dead-connection recovery
|
||||
|
||||
### Fixed
|
||||
- **Heartbeat now actually detects dead connections** (Discord report by CrusherEAGLE): The `ping`/`pong` heartbeat was being sent every 10s but neither side tracked whether responses were arriving, so a half-open TCP connection (common on Windows after sleep/wake, VPN toggle, or a brief editor hang) left both sides holding a dead socket. `isConnected()` continued to return `true`, every command timed out at 30s, and the only way back was to restart Claude Code **and** the Godot editor. Fixed on both sides:
|
||||
- **Server**: tracks the last `pong` timestamp; if 30s passes with no pong, forcibly destroys the socket (`terminate()`, vs `close()` which waits for a FIN ack that never comes on a dead link). Pending requests are rejected immediately rather than hanging for 30s.
|
||||
- **Editor**: sends its own `ping` every 5s, tracks per-port inactivity, and after 30s of inbound silence force-closes the peer so the existing 3s reconnect cycle takes over.
|
||||
- **OS-level TCP keepalive** enabled on the server socket (5s initial delay), surfacing half-open links faster than Windows' ~2-hour default.
|
||||
- **Status panel surfaces stale state**: New yellow ⚠ indicator when a port is reconnecting from a stale state, plus per-port idle time (seconds since last received message) in the Clients tab. No more "looks fine while everything is broken" UI.
|
||||
|
||||
### Notes
|
||||
- Recovery is automatic within ~30s after the connection dies. Watch the Output panel for `[MCP] Port NNNN silent for X.Xs — forcing reconnect` if you want to see it happen.
|
||||
- No API or tool changes — same 172 tools, same behavior in the healthy path.
|
||||
|
||||
---
|
||||
|
||||
## v1.13.0 — 2026-05-05
|
||||
|
||||
**Bug Fixes & Polish** — Mouse motion dispatch, setup config, site pricing
|
||||
|
||||
### Fixed
|
||||
- **`simulate_mouse_move` honors explicit `unhandled: false`** (#24, #25): Drag motions (`button_mask > 0`) auto-promote to `push_input` so camera-pan use cases bypass GUI consumption. But callers writing UI drag-and-drop tests need events to reach the GUI dispatcher (so `_get_drag_data` / `_drop_data` fire). Now: if the caller explicitly passes `unhandled: false`, that wins; auto-promotion only happens when `unhandled` was omitted. Default behavior preserved.
|
||||
- **v1.12.0 build blocker**: Restored missing `mcp/server/src/utils/load-instructions.ts` referenced by `index.ts` since v1.12.0. Fresh clones of v1.12.0 failed `npm run build` with `Cannot find module './utils/load-instructions.js'`. v1.13.0 now builds clean.
|
||||
|
||||
### Changed
|
||||
- **`setup` no longer pins `GODOT_MCP_PORT`** (#27): Generated MCP client config (Claude Desktop, Cursor, etc.) omits the `GODOT_MCP_PORT` env var so the server can auto-scan ports `6505–6509`. Pinning a fixed port caused silent failures when a stale process held the port. Users who need a fixed port can still set it manually.
|
||||
- **Site JSON-LD price → $15** (#26): Structured data on the landing page now reflects the current price.
|
||||
|
||||
### Improved
|
||||
- **README clarity** (issue #7 follow-up): More prominent note that the public repo ships the addon only — the MCP server is distributed via Buy Me a Coffee / itch.io.
|
||||
- **`build-release.sh` portability**: Falls back to system `zip` and `python3` when 7-Zip is not on PATH.
|
||||
|
||||
---
|
||||
|
||||
## v1.12.0 — 2026-04-19
|
||||
|
||||
**Feature** — Android Remote Deploy · **Bug Fix** — Runtime IPC on custom user dirs
|
||||
|
||||
### Added
|
||||
- **Android Remote Deploy** (3 tools, Full mode only — #20):
|
||||
- `list_android_devices` — wraps `adb devices -l`, returns serial/state/model/product. Resolves adb from Editor Settings > Export > Android > Adb, falling back to `adb` on PATH.
|
||||
- `get_android_preset_info` — reads metadata (package name, export path, runnable) from an Android preset in `export_presets.cfg`.
|
||||
- `deploy_to_android` — one-shot pipeline: Godot CLI export → `adb install -r` → `adb shell monkey` launch. Options: `preset_name`, `device_serial`, `debug`, `launch`, `skip_export`. Synchronous; export step can take tens of seconds.
|
||||
- Full mode tool count: **169 → 172**. LITE / 3D / MINIMAL modes are unchanged.
|
||||
|
||||
### Fixed
|
||||
- **`get_game_user_dir()`** (`commands/base_command.gd`) — #21: Runtime IPC commands (`get_game_scene_tree`, `get_game_node_properties`, `simulate_*`, etc.) failed with `Could not create game request file` when the project used `application/config/use_custom_user_dir=true`, or when `application/config/name` contained characters illegal on the host OS (e.g. `:` on Windows). Editor and game now resolve to the same dir: early-return `OS.get_user_data_dir()` for custom user dirs, and sanitize `config/name` via `xml_unescape().validate_filename().replace(".", "_")` — matching Godot's own logic in `ProjectSettings::_init`. Thanks @asim9834 for the detailed repro + patch.
|
||||
|
||||
---
|
||||
|
||||
## v1.11.0 — 2026-04-15
|
||||
|
||||
**Feature** — New `--3d` mode for 100-tool-limit clients
|
||||
|
||||
### Added
|
||||
- **`--3d` mode**: Registers exactly 100 tools — the 81 core LITE tools plus Physics (6), AnimationTree (8), and Navigation (5). Designed for clients with a 100-tool cap (e.g. Google Antigravity with Claude Code proxy) that need full 3D game development capabilities. Usage: `node build/index.js --3d`
|
||||
|
||||
### Improved
|
||||
- **Troubleshooting docs**: Clarified port conflict advice in INSTALL.md — recommends letting the server auto-scan ports 6505–6509 instead of setting a fixed `GODOT_MCP_PORT`, which can cause silent failures with stale processes
|
||||
|
||||
### Mode comparison
|
||||
|
||||
| Flag | Tools | Use case |
|
||||
|------|-------|----------|
|
||||
| *(none)* | 169 | Full mode — all tools |
|
||||
| `--3d` | 100 | 3D game dev under 100-tool limit |
|
||||
| `--lite` | 81 | Tight tool limits (Cursor, etc.) |
|
||||
| `--minimal` | 35 | Ultra-tight limits (local LLMs) |
|
||||
|
||||
---
|
||||
|
||||
## v1.10.3 — 2026-04-11
|
||||
|
||||
**Bug Fixes** — Autoload preservation, Windows build, port conflict warning
|
||||
|
||||
### Fixed
|
||||
- **Autoload deletion on `--import` / shutdown**: Plugin no longer removes pre-existing MCP autoloads from `project.godot`. Previously, `_remove_autoloads()` deleted all managed autoload keys unconditionally — even if they were project-owned. Now only autoloads injected by the current plugin session are removed. (#17)
|
||||
- **Windows build failure**: Removed Unix-only `chmod -R a+x build || true` from the `build` script in `package.json`. The `chmod` and `true` commands don't exist on Windows cmd/PowerShell, causing `node build/setup.js install` to fail with "Build failed" even though TypeScript compilation succeeded. The fix is simply `"build": "tsc"` — execute permissions are not needed since the server runs via `node`. (#Discord)
|
||||
|
||||
### Improved
|
||||
- **Port conflict warning for explicit port**: When `GODOT_MCP_PORT` is set and the port is already occupied (e.g. by a stale process), the server now logs a clear warning with remediation steps instead of silently failing to bind. (#15)
|
||||
|
||||
---
|
||||
|
||||
## v1.10.2 — 2026-04-11
|
||||
|
||||
**Fix** — Linux permission issue for build files
|
||||
|
||||
### Fixed
|
||||
- **Linux permission denied**: Added `chmod -R a+x` to build process so that `build/index.js` and other compiled files have execute permission out of the box on Linux/macOS. Previously, users had to manually run `chmod -R a+x build` after installation. (Thanks to kflamsted for reporting!)
|
||||
|
||||
---
|
||||
|
||||
## v1.10.1 — 2026-04-08
|
||||
|
||||
**UX Improvement** — Bottom panel renamed, INSTALL.md rewritten
|
||||
|
||||
### Improved
|
||||
- **Bottom panel renamed**: "MCP Server" → "MCP Pro" for consistency with the product name. Status label also updated.
|
||||
- **INSTALL.md rewritten**: Added zip structure diagram, clear separation of addon vs server, Claude Desktop config paths, and better troubleshooting. Clarified that `configure` must be run from the Godot project directory.
|
||||
|
||||
---
|
||||
|
||||
## v1.10.0 — 2026-04-07
|
||||
|
||||
**New Tools & Quality Sweep** — Editor camera control, 169 tools, comprehensive audit fixes
|
||||
|
||||
### New Tools
|
||||
- **`get_editor_camera`**: Get the 3D editor viewport camera position, rotation, and FOV. Useful for understanding the current view before taking screenshots.
|
||||
- **`set_editor_camera`**: Move the 3D editor viewport camera to a specific position and orientation. Supports position, rotation, look_at target, and FOV. Use this to frame a view before screenshots to validate changes visually.
|
||||
|
||||
### Fixed
|
||||
- **`plugin.gd` version display**: Was hardcoded to "v1.6.0" since initial release. Now dynamically reads from `plugin.cfg` — always shows the correct version.
|
||||
- **Tool count inconsistency**: Was showing 162/163/167 across different files. All references now correctly say 169.
|
||||
- **`node setup.js` path**: All docs and help text now correctly say `node build/setup.js`.
|
||||
- **`configure` cwd issue**: INSTALL.md now clearly separates `install` (run from server/) and `configure` (run from Godot project root) to avoid `.mcp.json` being placed in the wrong directory.
|
||||
- **INSTALL.md**: Fixed step numbering skip, stale tool count (49→169), port range (6505-6514).
|
||||
- **README.md**: Replaced hardcoded dev paths with `/path/to/` placeholders.
|
||||
|
||||
### Improved
|
||||
- **"v1.x" wording removed**: All pricing and marketing text now says "lifetime updates" without version scope.
|
||||
- **Plugin port range**: WebSocket comment and connection range expanded to 6505-6514 (6510-6514 reserved for CLI).
|
||||
- **Pre-built JS in release zip**: `build/setup.js` and `build/cli.js` work immediately after extract + `npm install`.
|
||||
- **Claude Desktop support**: Confirmed working, added to configure auto-detection.
|
||||
|
||||
---
|
||||
|
||||
## v1.9.4 — 2026-04-06
|
||||
|
||||
**Bug Fixes** — State enum type regression, zip plugin version
|
||||
|
||||
### Fixed
|
||||
- **`mcp_game_inspector_service.gd` State enum type error (regression)**: `var _state: State = State.IDLE` caused "Cannot assign a value of type mcp_game_inspector_service.gd.State to variable with specified type State" in some Godot versions. Changed back to `var _state := State.IDLE` (type inference). This was originally fixed in v1.6.4 but regressed. (Thanks @kalish)
|
||||
- **Release zip contained wrong plugin version**: v1.9.3 zip shipped with plugin.cfg showing v1.9.2 due to a build order issue. Fixed build pipeline to ensure public repo is synced before zip creation.
|
||||
|
||||
---
|
||||
|
||||
## v1.9.3 — 2026-04-06
|
||||
|
||||
**Improvement** — Pre-built JS in release zip, docs cleanup
|
||||
|
||||
### Improved
|
||||
- **Pre-built JS files included in release zip**: `build/setup.js`, `build/cli.js`, and all other compiled files are now included. Users can run `node build/setup.js install` immediately after extracting — no need to manually run `npm run build` first.
|
||||
- **CLI naming unified in docs**: Removed `godot-cli` shorthand. All docs consistently use `node build/cli.js`. Added note that server must be built before CLI use.
|
||||
- **CLI help port range fixed**: `--help` output now correctly shows 6510-6514 (CLI range), not 6505-6509 (MCP server range).
|
||||
|
||||
---
|
||||
|
||||
## v1.9.2 — 2026-04-06
|
||||
|
||||
**New Features** — Setup CLI, code-to-inspector workflow, CLI click fix
|
||||
|
||||
### New Features
|
||||
- **Setup CLI (`setup.js`)**: One-command server setup and management. Commands: `install` (npm install + build), `check-update` (GitHub release check with semver comparison), `configure` (auto-detect AI client and generate .mcp.json), `doctor` (environment diagnostics).
|
||||
- **Code-to-inspector migration workflow**: New guideline in AGENTS.md and skills.md instructing AI to prefer `update_property` over hardcoded GDScript for visual properties (colors, sizes, theme overrides). Includes step-by-step migration pattern.
|
||||
|
||||
### Fixed
|
||||
- **CLI `input click --button` mapping**: The CLI sent string values ("left", "right", "middle") but the plugin expects numeric indices (1, 2, 3). Now correctly maps `left`→1, `right`→2, `middle`→3. (Thanks @Gogomy)
|
||||
|
||||
### Improved
|
||||
- **INSTALL.md**: Added quick setup flow using `setup.js` for both fresh install and updates.
|
||||
- **Instruction files**: All 12 client instruction files updated with new workflow patterns.
|
||||
|
||||
---
|
||||
|
||||
## v1.9.1 — 2026-04-05
|
||||
|
||||
**Bug Fix** — GODOT_MCP_PORT env var now respected + Cursor Full mode
|
||||
|
||||
### Fixed
|
||||
- **`GODOT_MCP_PORT` env var ignored**: The server always scanned ports 6505-6509 for the first free port, ignoring the explicitly configured port. Now when `GODOT_MCP_PORT` is set, the server uses that port directly without scanning. (Fixes #13)
|
||||
|
||||
### Changed
|
||||
- **Cursor moved to Full mode**: Cursor removed its 40-tool limit with Dynamic Context Discovery — all 167 tools now work in Full mode. (Thanks to @CrossBread for PR #14)
|
||||
|
||||
---
|
||||
|
||||
## v1.9.0 — 2026-04-05
|
||||
|
||||
**Universal Compatibility** — Minimal mode, CLI tool, and test suite
|
||||
|
||||
### New Features
|
||||
- **Minimal mode (`--minimal`)**: Registers only 35 essential tools for clients with tight tool limits (Cursor ~40, OpenCode, local LLMs with small context windows). Covers project info, scene management, node CRUD, script editing, editor errors, input simulation, and runtime inspection.
|
||||
- **CLI tool (`godot-cli`)**: Command-line interface for controlling Godot directly from a terminal. LLMs discover capabilities progressively via `--help` instead of loading all tool definitions upfront — zero context overhead, works with any client that has bash/terminal access. 7 command groups: project, scene, node, script, editor, input, runtime.
|
||||
- **Test suite**: Added vitest with 47 unit tests covering tool-filter, error utilities, zod coercion, and CLI help/error handling.
|
||||
|
||||
### Improved
|
||||
- **Client compatibility guide**: README and landing page now include a compatibility matrix for 12+ MCP clients with recommended mode for each (Full/Lite/Minimal/CLI).
|
||||
- **Landing page**: Added "Choose Your Mode" setup step, CLI documentation, and new FAQ entry for tool count limits.
|
||||
- **`print_verbose` for connect/disconnect**: WebSocket connect/disconnect messages in the Godot plugin now use `print_verbose()` instead of `print()`, eliminating terminal spam during normal operation.
|
||||
- **Per-client instruction files**: `instructions/` folder with ready-to-copy instruction files for 12 AI clients (Claude Code, Cursor, Cline, Windsurf, Gemini CLI, Codex CLI, OpenCode, Roo Code, JetBrains/Junie, Amazon Q, Continue, Augment). Includes CLI usage documentation.
|
||||
|
||||
---
|
||||
|
||||
## v1.8.1 — 2026-04-04
|
||||
|
||||
**Bug Fix** — @export node reference support in update_property
|
||||
|
||||
### Fixed
|
||||
- **`update_property` @export node references**: Setting `@export var` node references (e.g. `@export var hud: HUD`) via `update_property` now correctly resolves string paths to actual node references. Previously, `typeof(old_value)` returned `TYPE_NIL` for unset exports and `TYPE_OBJECT` for set ones, neither of which resolved the path string to a node. The fix checks `PROPERTY_HINT_NODE_TYPE` from the property's metadata to detect node reference exports and resolve accordingly. (Fixes #12)
|
||||
|
||||
---
|
||||
|
||||
## v1.8.0 — 2026-04-02
|
||||
|
||||
**New Features** — HTTP transport, screenshot file saving, custom class support
|
||||
|
||||
### New Features
|
||||
- **Streamable HTTP transport**: New `--http` and `--http-port` flags for MCP clients that need HTTP instead of stdio. Starts an HTTP server at `http://127.0.0.1:8001/mcp` (default port).
|
||||
- **Screenshot `save_path` option**: `get_editor_screenshot` and `get_game_screenshot` now accept an optional `save_path` parameter (e.g. `res://screenshot.png`) to save directly to disk instead of returning base64, avoiding MCP response cache bloat.
|
||||
- **`add_node` custom class support**: `add_node` now resolves script-defined classes (`class_name`) in addition to built-in ClassDB types via `ProjectSettings.get_global_class_list()`.
|
||||
|
||||
### Improved
|
||||
- **INSTALL.md**: Added "Updating to a New Version" section with step-by-step upgrade instructions.
|
||||
|
||||
---
|
||||
|
||||
## v1.7.2 — 2026-03-31
|
||||
|
||||
**Bug Fixes & Improvements** — execute_game_script robustness + auto-dismiss control
|
||||
|
||||
### Fixed
|
||||
- **`execute_game_script` mixed indentation error**: User code with space indentation was prepended with tabs, causing "Mixed use of tabs and spaces" parse errors. Now auto-detects indent width and normalizes all leading spaces to tabs before wrapping.
|
||||
- **`execute_game_script` standalone lambda error**: Top-level `func` definitions in user code were nested inside the wrapper's `run()` function, triggering "Standalone lambdas cannot be accessed" parse errors. Now extracts top-level functions to class level.
|
||||
- **`command_router` crash on missing config section**: `_load_tool_config()` called `get_section_keys("disabled_tools")` without checking if the section exists, causing "Cannot get keys from nonexistent section" errors on fresh installs.
|
||||
|
||||
### Changed
|
||||
- **Auto-dismiss dialogs now opt-in**: Previously auto-dismissed blocking editor dialogs whenever an MCP client was connected. Now disabled by default — AI must explicitly enable via the new `set_auto_dismiss` tool before operations that trigger reload/save dialogs.
|
||||
|
||||
### New Tools
|
||||
- **`set_auto_dismiss`**: Enable or disable automatic dismissal of blocking editor dialogs (e.g., "Reload from disk?", "Save changes?"). Use before external file modifications, disable when done.
|
||||
|
||||
---
|
||||
|
||||
## v1.7.1 — 2026-03-30
|
||||
|
||||
**Bug Fixes** — Scene transition crash fix and deprecated API cleanup
|
||||
|
||||
### Fixed
|
||||
- **`click_button_by_text` crash on scene transition**: Clicking a button that triggers a scene change (e.g., navigating from main menu to options) caused "Cannot get path of node as it is not in a scene tree" errors. Now caches button info before emitting the pressed signal and guards with `is_instance_valid()` / `is_inside_tree()` after the click.
|
||||
- **Deprecated `push_unhandled_input()` warning**: Replaced with `push_input()` in `mcp_input_service.gd` per Godot 4.x API updates.
|
||||
|
||||
---
|
||||
|
||||
## v1.7.0 — 2026-03-29
|
||||
|
||||
**New Tools & Multi-Client Support** — 3 new tools for faster scene building, runtime signal debugging, and UI layout + instructions for non-Claude AI clients
|
||||
|
||||
### New Tools
|
||||
- **`batch_add_nodes`**: Add multiple nodes in a single call. Nodes are processed in order so earlier nodes can be referenced as parents — build entire node trees in one shot instead of calling `add_node` repeatedly.
|
||||
- **`watch_signals`**: Monitor signal emissions on specified nodes in the running game for a set duration. Returns a timestamped log of every signal fired with arguments — great for debugging event flow and verifying signal connections.
|
||||
- **`setup_control`**: Configure a Control/Container node's layout in one call: anchor preset, min size, size flags, margins (MarginContainer), separation (VBox/HBoxContainer), and grow direction. Replaces 5+ `update_property` calls.
|
||||
|
||||
### New
|
||||
- **`AGENTS.md` template**: Custom instructions for non-Claude AI clients (OpenAI Codex, opencode/ollama, Cursor, etc.). Includes editor vs runtime tool categorization, workflow patterns, formatting rules, and common pitfalls. Included in release zip.
|
||||
|
||||
---
|
||||
|
||||
## v1.6.5 — 2026-03-27
|
||||
|
||||
**assert_node_state Fix** — Game-side handler was missing, causing "Unknown command" error
|
||||
|
||||
### Fixed
|
||||
- **`assert_node_state` missing game-side handler**: The command was registered in the TypeScript server and editor-side GDScript, but `mcp_game_inspector_service.gd` had no handler — returning "Unknown command" at runtime. This also broke node assertions within `run_test_scenario`. All 8 operators (eq, neq, gt, lt, gte, lte, contains, type_is) now work correctly.
|
||||
- **Sub-property access in assertions**: Properties like `position:y` now use `get_indexed()` instead of `get()`, enabling assertions on vector components and nested properties.
|
||||
|
||||
---
|
||||
|
||||
## v1.6.4 — 2026-03-25
|
||||
|
||||
**Enum Type Fix** — Fixes script error on play in certain Godot versions
|
||||
|
||||
### Fixed
|
||||
- **`mcp_game_inspector_service.gd` State enum type error**: Explicit `State` type annotation on `_state` variable caused "Cannot assign a value of type mcp_game_inspector_service.gd.State to variable with specified type State" errors in some Godot versions. Changed to type inference (`:=`) which resolves the mismatch.
|
||||
|
||||
---
|
||||
|
||||
## v1.6.3 — 2026-03-24
|
||||
|
||||
**Camera Pan Fix** — Mouse drag events now bypass GUI layer to reach `_unhandled_input()`
|
||||
|
||||
### Fixed
|
||||
- **Mouse drag not reaching `_unhandled_input()`**: `simulate_mouse_move` with `button_mask` (drag simulation) was consumed by GUI Controls (`mouse_filter=STOP`) before reaching `_unhandled_input()`. Camera pan, drag-to-select, and other drag-based mechanics that rely on `_unhandled_input()` now work correctly. Events with `button_mask > 0` automatically use `push_unhandled_input()` to bypass the GUI layer.
|
||||
|
||||
### New
|
||||
- **`simulate_mouse_move` `unhandled` parameter**: Optional `unhandled` flag to force any mouse motion event to bypass GUI and go directly to `_unhandled_input()`. Auto-enabled when `button_mask > 0`.
|
||||
- **`simulate_sequence` `unhandled` support**: Sequence `mouse_motion` events also support the `unhandled` flag.
|
||||
|
||||
---
|
||||
|
||||
## v1.6.2 — 2026-03-24
|
||||
|
||||
**Animation Easing & Mouse Drag Simulation** — Community-requested fixes
|
||||
|
||||
### New
|
||||
- **`set_animation_keyframe` easing parameter**: Optional `easing` param (default 1.0) to control keyframe transition curves. Values: 1.0=linear, <1.0=ease-in, >1.0=ease-out, negative=in-out variants.
|
||||
- **`get_animation_info` easing field**: Each keyframe now returns its `easing` value.
|
||||
- **`simulate_mouse_move` button_mask**: New `button_mask` parameter (1=left, 2=right, 4=middle) enables drag simulation. Required for games that check `InputEventMouseMotion.button_mask` (e.g. camera pan with mouse drag).
|
||||
- **`simulate_sequence` button_mask**: Sequence events also support `button_mask` for drag operations.
|
||||
|
||||
### Fixed
|
||||
- **Mouse sequence events**: `simulate_sequence` now correctly handles flat key format (`relative_x`, `relative_y`, `x`, `y`) in addition to nested format. Previously, mouse motion events in sequences had `relative=(0,0)` because the flat-to-nested conversion was missing.
|
||||
|
||||
---
|
||||
|
||||
## v1.6.1 — 2026-03-21
|
||||
|
||||
**Permission Presets** — Auto-approve tool permissions for Claude Code
|
||||
|
||||
### New
|
||||
- **`settings.local.json`** (conservative): Pre-configured permission file that auto-approves 152 of 163 tools. Destructive tools (`delete_node`, `delete_scene`, `execute_editor_script`, etc.) still require manual approval.
|
||||
- **`settings.local.permissive.json`**: Allows all 163 tools and all Bash commands, with an explicit deny list for dangerous shell commands (`rm -rf`, `git push --force`, `git reset --hard`, etc.) and destructive MCP tools.
|
||||
- Copy either file to `~/.claude/settings.local.json` to skip per-tool permission prompts.
|
||||
|
||||
---
|
||||
|
||||
## v1.6.0 — 2026-03-21
|
||||
|
||||
**Enhanced Editor Panel** — Activity log with response details, client monitor, and tool management
|
||||
|
||||
### New
|
||||
- **Activity tab**: Full command log showing method name, status, port, and timestamp. Toggle "Show Response Details" to inspect the JSON responses sent back to AI clients. Clear button to reset the log.
|
||||
- **Clients tab**: Real-time view of all 5 WebSocket ports (6505-6509) with connection status and elapsed time since connection.
|
||||
- **Tools tab**: Searchable list of all 163 tools with individual enable/disable checkboxes. Bulk "Enable All" / "Disable All" buttons. Disabled tools are persisted across sessions (`user://mcp_tool_config.cfg`) and return a clear error message to AI clients.
|
||||
|
||||
### Changed
|
||||
- Status panel rebuilt with TabContainer (Activity / Clients / Tools)
|
||||
- WebSocket server now emits `command_completed` signal with full response data and source port
|
||||
- Connection time tracking per port for uptime display
|
||||
|
||||
---
|
||||
|
||||
## v1.5.3 — 2026-03-15
|
||||
|
||||
**New tool** — `record_frames` for long-running debug observation
|
||||
|
||||
### New
|
||||
- **`record_frames`**: Capture up to 600 screenshots saved as PNG files to `user://mcp_recorded_frames/`. Unlike `capture_frames` (which returns base64 images directly, max 30), this tool saves to disk and returns file paths — ideal for long-running debug sessions without flooding the AI context with image data. Supports optional `node_data` tracking for per-frame property snapshots (position, velocity, etc.).
|
||||
|
||||
---
|
||||
|
||||
## v1.5.2 — 2026-03-13
|
||||
|
||||
**Bugfix** — Screenshot capture now works when the SceneTree is paused
|
||||
|
||||
### Fixed
|
||||
- **`mcp_screenshot_service.gd`**: Added `process_mode = Node.PROCESS_MODE_ALWAYS` so the file-polling loop in `_process()` keeps running during pause. The other two autoloads (`mcp_input_service.gd`, `mcp_game_inspector_service.gd`) already had this — screenshot service was the only one missing it.
|
||||
- **`mcp_screenshot_service.gd`**: Replaced `await get_tree().process_frame` with `await get_tree().create_timer(0.05).timeout` — `process_frame` never fires when the tree is paused, but `create_timer()` with default `process_always=true` does.
|
||||
|
||||
Thanks to **mrkielbasa** for reporting this bug!
|
||||
|
||||
---
|
||||
|
||||
## v1.5.1 — 2026-03-08
|
||||
|
||||
**Patch release** — AI Skills file for better out-of-the-box experience
|
||||
|
||||
### New
|
||||
- **`skills.md`**: Added `addons/godot_mcp/skills.md` — a comprehensive guide for AI assistants covering all 162 tools, 10 practical workflows, best practices, and common pitfalls. Users can copy this to `.claude/skills.md` in their project root so Claude Code knows how to use the MCP tools effectively from the start.
|
||||
- **README**: Added setup step for copying `skills.md` to `.claude/skills.md`.
|
||||
|
||||
---
|
||||
|
||||
## v1.5.0 — 2026-03-04
|
||||
|
||||
**Feature** — Lite mode for MCP clients with tool count limits
|
||||
|
||||
### New Features
|
||||
- **Lite mode (`--lite`)**: Launch with `--lite` flag to register only 76 core tools instead of 162. Designed for MCP clients with tool count limits (Windsurf: 100, Cursor: ~40, Antigravity: 100).
|
||||
- Core categories (always loaded): project, scene, node, script, editor, input, runtime, input_map
|
||||
- Extended categories (Full mode only): animation, animation_tree, audio, batch, export, navigation, particle, physics, profiling, resource, scene_3d, shader, test, theme, tilemap, analysis
|
||||
- Usage: Add `"--lite"` to args in your MCP config
|
||||
|
||||
---
|
||||
|
||||
## v1.4.5 — 2026-03-04
|
||||
|
||||
**Patch release** — Godot 4.3 compatibility fix
|
||||
|
||||
### Bug Fixes
|
||||
- **Godot 4.3 compatibility**: Fixed `scene_3d_commands.gd` parse error caused by `Environment.TONE_MAPPER_AGX` enum (added in Godot 4.4). Now uses integer value for backward compatibility. This was a blocking error that prevented the entire plugin from loading on Godot 4.3.
|
||||
|
||||
---
|
||||
|
||||
## v1.4.4 — 2026-03-04
|
||||
|
||||
**Patch release** — Revert Output panel filter expansion
|
||||
|
||||
### Bug Fixes
|
||||
- **`get_editor_errors`**: Removed `W `, `WARN`, `GDScript` Output panel filters added in v1.4.3 — these patterns don't actually appear in Godot's Output panel (`push_warning` uses `WARNING:` prefix) and caused false positives on normal text.
|
||||
|
||||
---
|
||||
|
||||
## v1.4.3 — 2026-03-04
|
||||
|
||||
**Patch release** — Comprehensive error/warning detection
|
||||
|
||||
### Improvements
|
||||
- **`get_editor_errors`**: Now reads runtime errors from the debugger Errors tab (ScriptEditorDebugger), returned with `DEBUGGER:` prefix including stack traces. Previously only static analysis and Output panel errors were captured.
|
||||
|
||||
### Bug Fixes
|
||||
- **`get_editor_errors`**: Fixed debugger Errors tab not being found because the tab name includes a count suffix (e.g. "Errors (1)") — now uses prefix matching.
|
||||
|
||||
---
|
||||
|
||||
## v1.4.2 — 2026-03-04
|
||||
|
||||
**Patch release** — Improved error detection in script editor
|
||||
|
||||
### Improvements
|
||||
- **`get_editor_errors`**: Now reads GDScript analyzer errors and warnings from the script editor's error/warning panels (VSplitContainer RichTextLabels), in addition to the Output panel and CodeEdit line highlights. Catches static analysis messages like type mismatches and autoload name conflicts that were previously missed.
|
||||
|
||||
---
|
||||
|
||||
## v1.4.1 — 2026-03-02
|
||||
|
||||
**Patch release** — Bug fixes found during comprehensive tool audit
|
||||
|
||||
### Bug Fixes
|
||||
- **`replay_recording`**: Fixed false crash recovery error (`_pending_command` flag not cleared for async replay loop)
|
||||
- **`wait_for_node`**: Fixed false crash recovery error when polling for node appearance
|
||||
- **`apply_particle_preset`**: Fixed editor crash in `gl_compatibility` renderer caused by immediate `GradientTexture1D` assignment — now uses `set_deferred` and reduced texture width
|
||||
|
||||
---
|
||||
|
||||
## v1.4.0 — 2026-03-01
|
||||
|
||||
**162 tools** across 23 categories (+15 new tools)
|
||||
|
||||
### New Tools
|
||||
- **`move_to`** — Autopilot: automatically walk a character to target coordinates using pathfinding
|
||||
- **`navigate_to`** — High-level navigation command for AI-driven movement
|
||||
- **`find_nearby_nodes`** — Find nodes within a radius of a given position
|
||||
- **`get_node_groups`** / **`set_node_groups`** — Read and write node group memberships
|
||||
- **`find_nodes_in_group`** — Query all nodes belonging to a specific group
|
||||
- **`get_output_log`** — Retrieve Godot's Output panel contents
|
||||
- **`get_input_actions`** / **`set_input_action`** — Read and configure Input Map actions
|
||||
- **`search_in_files`** — Full-text search across project files
|
||||
- **`validate_script`** — Check GDScript for errors without running
|
||||
- **`get_resource_preview`** — Get thumbnail previews of resources
|
||||
- **`get_scene_exports`** — List exported variables in a scene's root script
|
||||
- **`add_autoload`** / **`remove_autoload`** — Manage autoload singletons
|
||||
|
||||
### Bug Fixes & Improvements
|
||||
- **Crash recovery**: `capture_frames` no longer triggers false crash recovery (`_pending_command` flag fix)
|
||||
- **`capture_frames` node_data**: Optional per-frame property snapshots via `node_data` parameter
|
||||
- **Debugger auto-continue**: Automatically presses Continue when runtime errors pause the debugger
|
||||
- **`simulate_key` duration**: Now accepts fractional seconds (e.g., 0.3s) for precise movement
|
||||
- **Command router fix**: All 8 command classes now properly registered (~47 tools were previously unreachable)
|
||||
|
||||
---
|
||||
|
||||
## v1.3.1 — 2026-02-27
|
||||
|
||||
**Patch release**
|
||||
|
||||
### Bug Fixes
|
||||
- **`get_editor_errors`**: Now reads from Output panel and CodeEdit error gutter (previously returned empty results)
|
||||
- **Tonemap enum**: Fixed environment tonemap mode enum name mapping
|
||||
|
||||
---
|
||||
|
||||
## v1.3.0 — 2026-02-26
|
||||
|
||||
**147 tools** across 23 categories (+63 new tools)
|
||||
|
||||
### New Tool Categories
|
||||
|
||||
#### AnimationTree & State Machine (8 tools)
|
||||
- `create_animation_tree`, `get_animation_tree_structure`, `set_tree_parameter`
|
||||
- `add_state_machine_state`, `remove_state_machine_state`
|
||||
- `add_state_machine_transition`, `remove_state_machine_transition`
|
||||
- `set_blend_tree_node`
|
||||
|
||||
#### Physics & Collision (6 tools)
|
||||
- `setup_collision`, `setup_physics_body`, `get_collision_info`
|
||||
- `get_physics_layers`, `set_physics_layers`, `add_raycast`
|
||||
|
||||
#### 3D Scene (6 tools)
|
||||
- `add_mesh_instance`, `setup_lighting`, `set_material_3d`
|
||||
- `setup_environment`, `setup_camera_3d`, `add_gridmap`
|
||||
|
||||
#### Particles (5 tools)
|
||||
- `create_particles`, `set_particle_material`, `set_particle_color_gradient`
|
||||
- `get_particle_info`, `apply_particle_preset` (8 built-in presets: fire, smoke, sparks, rain, snow, explosion, magic, dust)
|
||||
|
||||
#### Navigation (5 tools)
|
||||
- `setup_navigation_region`, `bake_navigation_mesh`, `setup_navigation_agent`
|
||||
- `set_navigation_layers`, `get_navigation_info`
|
||||
|
||||
#### Audio (6 tools)
|
||||
- `get_audio_bus_layout`, `add_audio_bus`, `set_audio_bus`
|
||||
- `add_audio_bus_effect`, `add_audio_player`, `get_audio_info`
|
||||
|
||||
#### Testing & QA (5 tools)
|
||||
- `run_test_scenario`, `assert_node_state`, `assert_screen_text`
|
||||
- `run_stress_test`, `get_test_report`
|
||||
|
||||
#### Project Analysis (6 tools)
|
||||
- `find_unused_resources`, `analyze_signal_flow`, `analyze_scene_complexity`
|
||||
- `detect_circular_dependencies`, `get_scene_dependencies`, `get_project_statistics`
|
||||
|
||||
### Expanded: Runtime Analysis
|
||||
- `find_ui_elements`, `click_button_by_text`, `wait_for_node`
|
||||
- Runtime tools expanded from 4 to 15 tools
|
||||
|
||||
### Other Additions
|
||||
- `add_resource`, `create_resource`, `edit_resource`, `read_resource` — Resource management tools
|
||||
|
||||
---
|
||||
|
||||
## v1.2.0 — 2026-02-24
|
||||
|
||||
**84 tools** across 14 categories (+34 new tools)
|
||||
|
||||
### New Tool Categories
|
||||
|
||||
#### Animation (6 tools)
|
||||
- `list_animations`, `create_animation`, `add_animation_track`
|
||||
- `set_animation_keyframe`, `get_animation_info`, `remove_animation`
|
||||
|
||||
#### TileMap (6 tools)
|
||||
- `tilemap_set_cell`, `tilemap_fill_rect`, `tilemap_get_cell`
|
||||
- `tilemap_clear`, `tilemap_get_info`, `tilemap_get_used_cells`
|
||||
|
||||
#### Theme & UI (6 tools)
|
||||
- `create_theme`, `set_theme_color`, `set_theme_constant`
|
||||
- `set_theme_font_size`, `set_theme_stylebox`, `get_theme_info`
|
||||
|
||||
#### Profiling (2 tools)
|
||||
- `get_performance_monitors`, `get_editor_performance`
|
||||
|
||||
#### Batch Operations & Refactoring (5 tools)
|
||||
- `find_nodes_by_type`, `find_signal_connections`, `batch_set_property`
|
||||
- `find_node_references`, `get_scene_dependencies`
|
||||
|
||||
#### Shader (6 tools)
|
||||
- `create_shader`, `read_shader`, `edit_shader`
|
||||
- `assign_shader_material`, `set_shader_param`, `get_shader_params`
|
||||
|
||||
#### Export (3 tools)
|
||||
- `list_export_presets`, `export_project`, `get_export_info`
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed game IPC connection when project name changes
|
||||
- Added `set_project_setting` tool for safe project.godot modifications via EditorSettings API
|
||||
- Fixed script reload behavior
|
||||
|
||||
---
|
||||
|
||||
## v1.1.0 — 2026-02-23
|
||||
|
||||
**49 tools** across 8 categories (+16 new tools)
|
||||
|
||||
### New Tool Categories
|
||||
|
||||
#### Input Simulation (4 tools)
|
||||
- `simulate_key`, `simulate_mouse_click`, `simulate_mouse_move`, `simulate_sequence`
|
||||
|
||||
#### Runtime Analysis (4 tools)
|
||||
- `play_scene`, `stop_scene`, `get_game_scene_tree`, `get_game_screenshot`
|
||||
- `execute_game_script`, `get_game_node_properties`, `set_game_node_property`
|
||||
- `monitor_properties`, `capture_frames`
|
||||
|
||||
### Other
|
||||
- Added `build-release.sh` for reproducible release packaging
|
||||
- `start_recording` / `stop_recording` / `replay_recording` for input recording
|
||||
|
||||
---
|
||||
|
||||
## v1.0.0 — 2026-02-22
|
||||
|
||||
**~33 tools** across 6 categories — Initial release
|
||||
|
||||
### Tool Categories
|
||||
- **Scene Management**: `create_scene`, `open_scene`, `save_scene`, `get_scene_tree`, `delete_scene`, `get_scene_file_content`, `add_scene_instance`
|
||||
- **Node Operations**: `add_node`, `delete_node`, `rename_node`, `move_node`, `duplicate_node`, `update_property`, `get_node_properties`, `batch_get_properties`, `connect_signal`, `disconnect_signal`, `get_signals`
|
||||
- **Script**: `create_script`, `read_script`, `edit_script`, `attach_script`, `list_scripts`, `find_nodes_by_script`, `find_script_references`, `get_open_scripts`
|
||||
- **Editor**: `get_editor_screenshot`, `get_editor_errors`, `clear_output`, `execute_editor_script`, `reload_plugin`
|
||||
- **Project**: `get_project_info`, `get_project_settings`, `get_filesystem_tree`, `search_files`
|
||||
- **UI**: Anchor presets (`set_anchor_preset`)
|
||||
|
||||
### Architecture
|
||||
- WebSocket-based communication between Godot editor plugin and MCP TypeScript server
|
||||
- Supports Claude Code, Cursor, Windsurf, and any MCP-compatible AI coding tool
|
||||
- Screenshot capture from both editor and game viewports
|
||||
@@ -0,0 +1,121 @@
|
||||
# Godot MCP Pro - Installation Guide
|
||||
|
||||
## What's in the zip
|
||||
|
||||
```
|
||||
godot-mcp-pro/
|
||||
├── addons/godot_mcp/ ← Godot plugin (copy into your Godot project)
|
||||
├── server/ ← MCP server (keep anywhere, runs alongside Godot)
|
||||
├── instructions/ ← AI client instruction files (optional)
|
||||
├── INSTALL.md ← This file
|
||||
├── README.md
|
||||
├── CHANGELOG.md
|
||||
└── ...
|
||||
```
|
||||
|
||||
The **addon** and **server** are two separate pieces:
|
||||
- **Addon** → goes inside your Godot project
|
||||
- **Server** → stays wherever you extracted it (does NOT go inside Godot)
|
||||
|
||||
## Step 1: Install the Godot Plugin
|
||||
|
||||
Copy the `addons/godot_mcp/` folder from the zip into your Godot project's `addons/` directory.
|
||||
|
||||
Enable the plugin in Godot:
|
||||
**Project → Project Settings → Plugins → Godot MCP Pro → Enable**
|
||||
|
||||
You should see "MCP Pro" in the bottom panel with a green connection dot.
|
||||
|
||||
> **Note**: You do NOT need to download anything from the Godot Asset Library. The paid zip includes everything.
|
||||
|
||||
## Step 2: Build the MCP Server
|
||||
|
||||
The server requires **Node.js 18+**. Check with `node --version`.
|
||||
|
||||
Open a terminal and run from the `server/` directory inside the extracted zip:
|
||||
|
||||
```bash
|
||||
cd /path/to/extracted/server
|
||||
node build/setup.js install
|
||||
```
|
||||
|
||||
This runs `npm install` (downloads dependencies) and `npm run build` (compiles TypeScript).
|
||||
|
||||
You can verify everything is working with:
|
||||
```bash
|
||||
node build/setup.js doctor
|
||||
```
|
||||
|
||||
## Step 3: Configure Your AI Client
|
||||
|
||||
Run this from your **Godot project** directory (not the server directory):
|
||||
|
||||
```bash
|
||||
cd /path/to/your/godot-project
|
||||
node /path/to/extracted/server/build/setup.js configure
|
||||
```
|
||||
|
||||
This auto-detects your AI client and creates a `.mcp.json` file in your project.
|
||||
|
||||
### Manual Configuration
|
||||
|
||||
If you prefer to configure manually, add this to your project's `.mcp.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"godot-mcp-pro": {
|
||||
"command": "node",
|
||||
"args": ["/path/to/extracted/server/build/index.js"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Replace `/path/to/extracted/` with the actual path where you extracted the zip.
|
||||
|
||||
### Claude Desktop
|
||||
|
||||
For Claude Desktop, add the same config to:
|
||||
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
||||
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
||||
|
||||
## Step 4: Use It
|
||||
|
||||
1. Open your Godot project with the plugin enabled
|
||||
2. Start your AI client (Claude Code, Cursor, Cline, etc.) in your project directory
|
||||
3. Ask the AI to interact with your Godot editor
|
||||
|
||||
The MCP Pro bottom panel in Godot shows connection status. A green dot means connected.
|
||||
|
||||
## Updating to a New Version
|
||||
|
||||
Check for updates:
|
||||
```bash
|
||||
node /path/to/server/build/setup.js check-update
|
||||
```
|
||||
|
||||
To update:
|
||||
1. Close Godot
|
||||
2. Replace `addons/godot_mcp/` in your Godot project with the new version from the zip
|
||||
3. Replace the `server/` folder with the new one, then rebuild:
|
||||
```bash
|
||||
cd /path/to/new/server
|
||||
node build/setup.js install
|
||||
```
|
||||
4. Reopen Godot
|
||||
|
||||
Your `.mcp.json` configuration stays the same — no need to reconfigure.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Plugin not connecting**: Make sure the MCP server is running (your AI client starts it automatically via `.mcp.json`)
|
||||
- **"Godot editor is not connected" error**: This is usually caused by a stale `node.exe` process from a previous session holding the port. Open Task Manager, kill all `node.exe` processes, then restart your AI client.
|
||||
- **Port conflict / `GODOT_MCP_PORT`**: Avoid setting a fixed `GODOT_MCP_PORT` in your config — the server auto-scans ports 6505–6509 and Godot connects to all of them automatically. A fixed port can cause silent failures if a stale process is already using it.
|
||||
- **Bottom panel shows "Waiting for connection"**: Start your AI client — it launches the MCP server which connects to Godot
|
||||
- **Need help?**: Contact abyo.software@gmail.com or join [Discord](https://discord.gg/zJ2u5zNUBZ)
|
||||
|
||||
## Documentation
|
||||
|
||||
- Landing page & tool reference: https://godot-mcp.abyo.net
|
||||
- Full tool list: `README.md`
|
||||
@@ -0,0 +1,42 @@
|
||||
Godot MCP Pro - Proprietary License
|
||||
|
||||
Copyright (c) 2026 Godot MCP Pro. All rights reserved.
|
||||
|
||||
This software and associated documentation files (the "Software") are the
|
||||
proprietary property of the copyright holder.
|
||||
|
||||
1. GRANT OF LICENSE
|
||||
Upon purchase, you are granted a non-exclusive, non-transferable,
|
||||
perpetual license to:
|
||||
- Use the Software for personal and commercial game development projects
|
||||
- Install the Software on any number of your own machines
|
||||
- Use the Software in any number of your own projects
|
||||
|
||||
2. RESTRICTIONS
|
||||
You may NOT:
|
||||
- Redistribute, sublicense, sell, or share the Software or any portion
|
||||
of its source code
|
||||
- Modify and redistribute the Software as a competing product
|
||||
- Remove or alter any copyright notices or license information
|
||||
- Share your license key or purchased files with others
|
||||
|
||||
3. UPDATES
|
||||
Your purchase includes lifetime access to updates for the version you
|
||||
purchased (1.x). Major version upgrades (2.x, 3.x) may require a
|
||||
separate purchase or upgrade fee.
|
||||
|
||||
4. NO WARRANTY
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
5. TERMINATION
|
||||
This license is automatically terminated if you violate any of these
|
||||
restrictions. Upon termination, you must destroy all copies of the
|
||||
Software in your possession.
|
||||
|
||||
For questions about licensing, contact: abyo.software@gmail.com
|
||||
@@ -0,0 +1,496 @@
|
||||
# Godot MCP Pro
|
||||
|
||||
Premium MCP (Model Context Protocol) server for AI-powered Godot game development. Connects AI assistants like Claude directly to your Godot editor with **172 powerful tools**.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
AI Assistant ←—stdio/MCP—→ Node.js Server ←—WebSocket:6505—→ Godot Editor Plugin
|
||||
```
|
||||
|
||||
- **Real-time**: WebSocket connection means instant feedback, no file polling
|
||||
- **Editor Integration**: Full access to Godot's editor API, UndoRedo system, and scene tree
|
||||
- **JSON-RPC 2.0**: Standard protocol with proper error codes and suggestions
|
||||
|
||||
## What's in this repo
|
||||
|
||||
> ⚠️ **This public repo only contains the free Godot addon/plugin.** The MCP server (Node.js, required to connect AI assistants) is distributed as part of the paid package — **one-time purchase**, lifetime updates:
|
||||
>
|
||||
> - **Buy Me a Coffee**: <https://buymeacoffee.com/y1uda/extras>
|
||||
> - **itch.io**: <https://y1uda.itch.io/godot-mcp-pro>
|
||||
>
|
||||
> The paid zip includes the addon, the `server/` directory with pre-built JavaScript, `INSTALL.md`, and AI-client instructions. If you cloned this repo and don't see a `server/` folder, **that's expected** — grab the full package from one of the links above.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Install the Godot Plugin
|
||||
|
||||
Copy the `addons/godot_mcp/` folder into your Godot project's `addons/` directory.
|
||||
|
||||
Enable the plugin: **Project → Project Settings → Plugins → Godot MCP Pro → Enable**
|
||||
|
||||
### 2. Install the MCP Server
|
||||
|
||||
> The `server/` directory is only included in the **full paid package** (see above). After downloading and extracting the zip, run:
|
||||
|
||||
```bash
|
||||
cd server
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
### 3. Configure Claude Code
|
||||
|
||||
Add to your `.mcp.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"godot-mcp-pro": {
|
||||
"command": "node",
|
||||
"args": ["/path/to/server/build/index.js"],
|
||||
"env": {
|
||||
"GODOT_MCP_PORT": "6505"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Choose Your Mode
|
||||
|
||||
Godot MCP Pro offers four modes to fit any client's tool limit:
|
||||
|
||||
| Mode | Tools | Best For |
|
||||
|------|-------|----------|
|
||||
| **Full** (default) | 172 | Claude Code, Cline, VS Code Copilot, Cursor |
|
||||
| **3D** (`--3d`) | 100 | Antigravity and other 100-tool-limit clients needing 3D |
|
||||
| **Lite** (`--lite`) | 81 | Windsurf, JetBrains Junie, Gemini CLI |
|
||||
| **Minimal** (`--minimal`) | 35 | OpenCode, local LLMs with small context |
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"godot-mcp-pro": {
|
||||
"command": "node",
|
||||
"args": ["/path/to/server/build/index.js", "--lite"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Replace `--lite` with `--minimal` for the smallest footprint.
|
||||
|
||||
- **Lite** includes: project, scene, node, script, editor, input, runtime, and input_map tools.
|
||||
- **Minimal** includes: 35 essential tools — project info, scene management, node CRUD, script editing, editor errors, input simulation, and runtime inspection.
|
||||
|
||||
### 5. CLI Mode (Alternative to MCP)
|
||||
|
||||
For clients without MCP support, or when you want zero context overhead, use the CLI directly from a terminal/bash tool. The CLI requires the server to be built first (Step 2).
|
||||
|
||||
```bash
|
||||
# Top-level help — shows all command groups
|
||||
node /path/to/server/build/cli.js --help
|
||||
|
||||
# Group help — shows commands in a group
|
||||
node /path/to/server/build/cli.js node --help
|
||||
|
||||
# Command help — shows options for a command
|
||||
node /path/to/server/build/cli.js node add --help
|
||||
|
||||
# Execute
|
||||
node /path/to/server/build/cli.js project info
|
||||
node /path/to/server/build/cli.js scene play
|
||||
node /path/to/server/build/cli.js node add --type CharacterBody3D --name Player
|
||||
```
|
||||
|
||||
Replace `/path/to/` with the actual path where you extracted the files.
|
||||
|
||||
The CLI connects directly to the Godot editor plugin via WebSocket. It requires:
|
||||
- Godot editor running with the MCP plugin enabled
|
||||
- Server built (`node build/setup.js install`)
|
||||
- An available port in the 6510-6514 range
|
||||
|
||||
**Advantage**: LLMs discover capabilities progressively via `--help` instead of loading all tool definitions upfront. This works with any LLM client that has terminal access, regardless of tool count limits.
|
||||
|
||||
### 6. Client Compatibility
|
||||
|
||||
| Client | Recommended Mode | Notes |
|
||||
|--------|-----------------|-------|
|
||||
| Claude Code | Full (default) | Deferred tool loading — minimal context cost |
|
||||
| VS Code Copilot | Full | Virtual Tools auto-group tools |
|
||||
| OpenAI Codex CLI | Full | MCPSearch defers overflow |
|
||||
| Cline | Full | No hard limit; use `enabledTools` to whitelist |
|
||||
| Roo Code | Full | No hard limit |
|
||||
| Windsurf | Lite | 100 tool limit |
|
||||
| JetBrains Junie | Lite | 100 tool limit |
|
||||
| Gemini CLI | Lite | ~100 client limit; use `excludeTools` for finer control |
|
||||
| Cursor | Full | Tool limit removed (Dynamic Context Discovery) |
|
||||
| OpenCode | Minimal or CLI | Models degrade past ~40 tools |
|
||||
| Local LLMs (LM Studio, etc.) | Minimal or CLI | Context window is the bottleneck |
|
||||
|
||||
### 7. Use It
|
||||
|
||||
Open your Godot project with the plugin enabled, then use Claude Code to interact with the editor.
|
||||
|
||||
## All 172 Tools
|
||||
|
||||
### Project Tools (7)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `get_project_info` | Project metadata, version, viewport, autoloads |
|
||||
| `get_filesystem_tree` | Recursive file tree with filtering |
|
||||
| `search_files` | Fuzzy/glob file search |
|
||||
| `get_project_settings` | Read project.godot settings |
|
||||
| `set_project_setting` | Set project settings via editor API |
|
||||
| `uid_to_project_path` | UID → res:// conversion |
|
||||
| `project_path_to_uid` | res:// → UID conversion |
|
||||
|
||||
### Scene Tools (9)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `get_scene_tree` | Live scene tree with hierarchy |
|
||||
| `get_scene_file_content` | Raw .tscn file content |
|
||||
| `create_scene` | Create new scene files |
|
||||
| `open_scene` | Open scene in editor |
|
||||
| `delete_scene` | Delete scene file |
|
||||
| `add_scene_instance` | Instance scene as child node |
|
||||
| `play_scene` | Run scene (main/current/custom) |
|
||||
| `stop_scene` | Stop running scene |
|
||||
| `save_scene` | Save current scene to disk |
|
||||
|
||||
### Node Tools (14)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `add_node` | Add node with type and properties |
|
||||
| `delete_node` | Delete node (with undo support) |
|
||||
| `duplicate_node` | Duplicate node and children |
|
||||
| `move_node` | Move/reparent node |
|
||||
| `update_property` | Set any property (auto type parsing) |
|
||||
| `get_node_properties` | Get all node properties |
|
||||
| `add_resource` | Add Shape/Material/etc to node |
|
||||
| `set_anchor_preset` | Set Control anchor preset |
|
||||
| `rename_node` | Rename a node in the scene |
|
||||
| `connect_signal` | Connect signal between nodes |
|
||||
| `disconnect_signal` | Disconnect signal connection |
|
||||
| `get_node_groups` | Get groups a node belongs to |
|
||||
| `set_node_groups` | Set node group membership |
|
||||
| `find_nodes_in_group` | Find all nodes in a group |
|
||||
|
||||
### Script Tools (8)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `list_scripts` | List all scripts with class info |
|
||||
| `read_script` | Read script content |
|
||||
| `create_script` | Create new script with template |
|
||||
| `edit_script` | Search/replace or full edit |
|
||||
| `attach_script` | Attach script to node |
|
||||
| `get_open_scripts` | List scripts open in editor |
|
||||
| `validate_script` | Validate GDScript syntax |
|
||||
| `search_in_files` | Search content in project files |
|
||||
|
||||
### Editor Tools (9)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `get_editor_errors` | Get errors and stack traces |
|
||||
| `get_editor_screenshot` | Capture editor viewport |
|
||||
| `get_game_screenshot` | Capture running game |
|
||||
| `execute_editor_script` | Run arbitrary GDScript in editor |
|
||||
| `clear_output` | Clear output panel |
|
||||
| `get_signals` | Get all signals of a node with connections |
|
||||
| `reload_plugin` | Reload the MCP plugin (auto-reconnect) |
|
||||
| `reload_project` | Rescan filesystem and reload scripts |
|
||||
| `get_output_log` | Get output panel content |
|
||||
|
||||
### Input Tools (7)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `simulate_key` | Simulate keyboard key press/release |
|
||||
| `simulate_mouse_click` | Simulate mouse click at position |
|
||||
| `simulate_mouse_move` | Simulate mouse movement |
|
||||
| `simulate_action` | Simulate Godot Input Action |
|
||||
| `simulate_sequence` | Sequence of input events with frame delays |
|
||||
| `get_input_actions` | List all input actions |
|
||||
| `set_input_action` | Create/modify input action |
|
||||
|
||||
### Runtime Tools (19)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `get_game_scene_tree` | Scene tree of running game |
|
||||
| `get_game_node_properties` | Node properties in running game |
|
||||
| `set_game_node_property` | Set node property in running game |
|
||||
| `execute_game_script` | Run GDScript in game context |
|
||||
| `capture_frames` | Multi-frame screenshot capture |
|
||||
| `monitor_properties` | Record property values over time |
|
||||
| `start_recording` | Start input recording |
|
||||
| `stop_recording` | Stop input recording |
|
||||
| `replay_recording` | Replay recorded input |
|
||||
| `find_nodes_by_script` | Find game nodes by script |
|
||||
| `get_autoload` | Get autoload node properties |
|
||||
| `batch_get_properties` | Batch get multiple node properties |
|
||||
| `find_ui_elements` | Find UI elements in game |
|
||||
| `click_button_by_text` | Click button by text content |
|
||||
| `wait_for_node` | Wait for node to appear |
|
||||
| `find_nearby_nodes` | Find nodes near position |
|
||||
| `navigate_to` | Navigate to target position |
|
||||
| `move_to` | Walk character to target |
|
||||
|
||||
### Animation Tools (6)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `list_animations` | List all animations in AnimationPlayer |
|
||||
| `create_animation` | Create new animation |
|
||||
| `add_animation_track` | Add track (value/position/rotation/method/bezier) |
|
||||
| `set_animation_keyframe` | Insert keyframe into track |
|
||||
| `get_animation_info` | Detailed animation info with all tracks/keys |
|
||||
| `remove_animation` | Remove an animation |
|
||||
|
||||
### TileMap Tools (6)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `tilemap_set_cell` | Set a single tile cell |
|
||||
| `tilemap_fill_rect` | Fill rectangular region with tiles |
|
||||
| `tilemap_get_cell` | Get tile data at cell |
|
||||
| `tilemap_clear` | Clear all cells |
|
||||
| `tilemap_get_info` | TileMapLayer info and tile set sources |
|
||||
| `tilemap_get_used_cells` | List of used cells |
|
||||
|
||||
### Theme & UI Tools (6)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `create_theme` | Create Theme resource file |
|
||||
| `set_theme_color` | Set theme color override |
|
||||
| `set_theme_constant` | Set theme constant override |
|
||||
| `set_theme_font_size` | Set theme font size override |
|
||||
| `set_theme_stylebox` | Set StyleBoxFlat override |
|
||||
| `get_theme_info` | Get theme overrides info |
|
||||
|
||||
### Profiling Tools (2)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `get_performance_monitors` | All performance monitors (FPS, memory, physics, etc.) |
|
||||
| `get_editor_performance` | Quick performance summary |
|
||||
|
||||
### Batch & Refactoring Tools (8)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `find_nodes_by_type` | Find all nodes of a type |
|
||||
| `find_signal_connections` | Find all signal connections in scene |
|
||||
| `batch_set_property` | Set property on all nodes of a type |
|
||||
| `find_node_references` | Search project files for pattern |
|
||||
| `get_scene_dependencies` | Get resource dependencies |
|
||||
| `cross_scene_set_property` | Set property across all scenes |
|
||||
| `find_script_references` | Find where script/resource is used |
|
||||
| `detect_circular_dependencies` | Find circular scene dependencies |
|
||||
|
||||
### Shader Tools (6)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `create_shader` | Create shader with template |
|
||||
| `read_shader` | Read shader file |
|
||||
| `edit_shader` | Edit shader (replace/search-replace) |
|
||||
| `assign_shader_material` | Assign ShaderMaterial to node |
|
||||
| `set_shader_param` | Set shader parameter |
|
||||
| `get_shader_params` | Get all shader parameters |
|
||||
|
||||
### Export Tools (3)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `list_export_presets` | List export presets |
|
||||
| `export_project` | Get export command for preset |
|
||||
| `get_export_info` | Export-related project info |
|
||||
|
||||
### Resource Tools (6)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `read_resource` | Read .tres resource properties |
|
||||
| `edit_resource` | Edit resource properties |
|
||||
| `create_resource` | Create new .tres resource |
|
||||
| `get_resource_preview` | Get resource thumbnail |
|
||||
| `add_autoload` | Register autoload singleton |
|
||||
| `remove_autoload` | Remove autoload singleton |
|
||||
|
||||
### Physics Tools (6)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `setup_physics_body` | Configure physics body properties |
|
||||
| `setup_collision` | Add collision shapes to nodes |
|
||||
| `set_physics_layers` | Set collision layer/mask |
|
||||
| `get_physics_layers` | Get collision layer/mask info |
|
||||
| `get_collision_info` | Get collision shape details |
|
||||
| `add_raycast` | Add RayCast2D/3D node |
|
||||
|
||||
### 3D Scene Tools (6)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `add_mesh_instance` | Add MeshInstance3D with primitive mesh |
|
||||
| `setup_camera_3d` | Configure Camera3D properties |
|
||||
| `setup_lighting` | Add/configure light nodes |
|
||||
| `setup_environment` | Configure WorldEnvironment |
|
||||
| `add_gridmap` | Set up GridMap node |
|
||||
| `set_material_3d` | Set StandardMaterial3D properties |
|
||||
|
||||
### Particle Tools (5)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `create_particles` | Create GPUParticles2D/3D |
|
||||
| `set_particle_material` | Configure ParticleProcessMaterial |
|
||||
| `set_particle_color_gradient` | Set color gradient for particles |
|
||||
| `apply_particle_preset` | Apply preset (fire, smoke, sparks, etc.) |
|
||||
| `get_particle_info` | Get particle system details |
|
||||
|
||||
### Navigation Tools (6)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `setup_navigation_region` | Configure NavigationRegion |
|
||||
| `setup_navigation_agent` | Configure NavigationAgent |
|
||||
| `bake_navigation_mesh` | Bake navigation mesh |
|
||||
| `set_navigation_layers` | Set navigation layers |
|
||||
| `get_navigation_info` | Get navigation setup info |
|
||||
|
||||
### Audio Tools (6)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `add_audio_player` | Add AudioStreamPlayer node |
|
||||
| `add_audio_bus` | Add audio bus |
|
||||
| `add_audio_bus_effect` | Add effect to audio bus |
|
||||
| `set_audio_bus` | Configure audio bus properties |
|
||||
| `get_audio_bus_layout` | Get audio bus layout info |
|
||||
| `get_audio_info` | Get audio-related node info |
|
||||
|
||||
### AnimationTree Tools (4)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `create_animation_tree` | Create AnimationTree |
|
||||
| `get_animation_tree_structure` | Get tree structure |
|
||||
| `set_tree_parameter` | Set AnimationTree parameter |
|
||||
| `add_state_machine_state` | Add state to state machine |
|
||||
|
||||
### State Machine Tools (3)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `remove_state_machine_state` | Remove state from state machine |
|
||||
| `add_state_machine_transition` | Add transition between states |
|
||||
| `remove_state_machine_transition` | Remove state transition |
|
||||
|
||||
### Blend Tree Tools (1)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `set_blend_tree_node` | Configure blend tree nodes |
|
||||
|
||||
### Analysis & Search Tools (4)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `analyze_scene_complexity` | Analyze scene performance |
|
||||
| `analyze_signal_flow` | Map signal connections |
|
||||
| `find_unused_resources` | Find unreferenced resources |
|
||||
| `get_project_statistics` | Get project-wide statistics |
|
||||
|
||||
### Testing & QA Tools (6)
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `run_test_scenario` | Run automated test scenario |
|
||||
| `assert_node_state` | Assert node property values |
|
||||
| `assert_screen_text` | Check for text on screen |
|
||||
| `compare_screenshots` | Compare two screenshots |
|
||||
| `run_stress_test` | Run performance stress test |
|
||||
| `get_test_report` | Get test results report |
|
||||
|
||||
## Key Features
|
||||
|
||||
- **UndoRedo Integration**: All node/property operations support Ctrl+Z
|
||||
- **Smart Type Parsing**: `"Vector2(100, 200)"`, `"#ff0000"`, `"Color(1,0,0)"` auto-converted
|
||||
- **Auto-Reconnect**: Exponential backoff reconnection (1s → 2s → 4s ... → 60s max)
|
||||
- **Heartbeat**: 10s ping/pong keeps connection alive
|
||||
- **Helpful Errors**: Error responses include suggestions for next steps
|
||||
|
||||
## Competitive Comparison
|
||||
|
||||
### Tool Count
|
||||
|
||||
| Category | Godot MCP Pro | GDAI MCP ($19) | tomyud1 (free) | Dokujaa (free) | Coding-Solo (free) | ee0pdt (free) | bradypp (free) |
|
||||
|----------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| Project | 7 | 5 | 4 | 0 | 2 | 2 | 2 |
|
||||
| Scene | 9 | 8 | 11 | 9 | 3 | 4 | 5 |
|
||||
| Node | **14** | 8 | 0 | 8 | 2 | 3 | 0 |
|
||||
| Script | **8** | 5 | 6 | 4 | 0 | 5 | 0 |
|
||||
| Editor | **9** | 5 | 1 | 5 | 1 | 3 | 2 |
|
||||
| Input | **7** | 2 | 0 | 0 | 0 | 0 | 0 |
|
||||
| Runtime | **19** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| Animation | **6** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| TileMap | **6** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| Theme/UI | **6** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| Profiling | **2** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| Batch/Refactor | **8** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| Shader | **6** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| Export | **3** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| Resource | **6** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| Physics | **6** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| 3D Scene | **6** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| Particle | **5** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| Navigation | **6** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| Audio | **6** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| AnimationTree | **4** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| State Machine | **3** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| Blend Tree | **1** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| Analysis | **4** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| Testing/QA | **6** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| Asset/AI | 0 | 0 | 1 | 6 | 0 | 0 | 0 |
|
||||
| Material | 0 | 0 | 0 | 2 | 0 | 0 | 0 |
|
||||
| Other | 0 | 0 | 9 | 5 | 5 | 2 | 1 |
|
||||
| Android Deploy | **3** | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| **Total** | **172** | ~30 | **32** | **39** | **13** | **19** | **10** |
|
||||
|
||||
### Feature Matrix
|
||||
|
||||
| Feature | Godot MCP Pro | GDAI MCP ($19) | tomyud1 (free) | Dokujaa (free) | Coding-Solo (free) |
|
||||
|---------|:---:|:---:|:---:|:---:|:---:|
|
||||
| **Connection** | WebSocket (real-time) | stdio (Python) | WebSocket | TCP Socket | Headless CLI |
|
||||
| **Undo/Redo** | Yes | Yes | No | No | No |
|
||||
| **JSON-RPC 2.0** | Yes | Custom | Custom | Custom | N/A |
|
||||
| **Auto-reconnect** | Yes (exponential backoff) | N/A | No | No | N/A |
|
||||
| **Heartbeat** | Yes (10s ping/pong) | No | No | No | No |
|
||||
| **Error suggestions** | Yes (contextual hints) | No | No | No | No |
|
||||
| **Screenshot capture** | Yes (editor + game) | Yes | No | No | No |
|
||||
| **Game input simulation** | Yes (key/mouse/action/sequence) | Yes (basic) | No | No | No |
|
||||
| **Runtime inspection** | Yes (scene tree + properties + monitor) | No | No | No | No |
|
||||
| **Signal management** | Yes (connect/disconnect/inspect) | No | No | No | No |
|
||||
| **Browser visualizer** | No | No | Yes | No | No |
|
||||
| **AI 3D mesh generation** | No | No | No | Yes (Meshy API) | No |
|
||||
|
||||
### Exclusive Categories (No Competitor Has These)
|
||||
|
||||
| Category | Tools | Why It Matters |
|
||||
|----------|-------|----------------|
|
||||
| **Animation** | 6 tools | Create animations, add tracks, set keyframes — all programmatically |
|
||||
| **TileMap** | 6 tools | Set cells, fill rects, query tile data — essential for 2D level design |
|
||||
| **Theme/UI** | 6 tools | StyleBox, colors, fonts — build UI themes without manual editor work |
|
||||
| **Profiling** | 2 tools | FPS, memory, draw calls, physics — performance monitoring |
|
||||
| **Batch/Refactor** | 8 tools | Find by type, batch property changes, cross-scene updates, dependency analysis |
|
||||
| **Shader** | 6 tools | Create/edit shaders, assign materials, set parameters |
|
||||
| **Export** | 3 tools | List presets, get export commands, check templates |
|
||||
| **Physics** | 6 tools | Set up collision shapes, bodies, raycasts, and layer management |
|
||||
| **3D Scene** | 6 tools | Add meshes, cameras, lights, environment, GridMap support |
|
||||
| **Particle** | 5 tools | Create particles with custom materials, presets, and gradients |
|
||||
| **Navigation** | 6 tools | Configure navigation regions, agents, pathfinding, baking |
|
||||
| **Audio** | 6 tools | Complete audio bus system, effects, players, live management |
|
||||
| **AnimationTree** | 4 tools | Build state machines with transitions and blend trees |
|
||||
| **State Machine** | 3 tools | Advanced state machine management for complex animations |
|
||||
| **Testing/QA** | 6 tools | Automated testing, assertions, stress testing, screenshot comparison |
|
||||
| **Runtime** | 19 tools | Inspect and control game at runtime: inspect, record, replay, navigate |
|
||||
|
||||
### Architecture Advantages
|
||||
|
||||
| Aspect | Godot MCP Pro | Typical Competitor |
|
||||
|--------|--------------|-------------------|
|
||||
| **Protocol** | JSON-RPC 2.0 (standard, extensible) | Custom JSON or CLI-based |
|
||||
| **Connection** | Persistent WebSocket with heartbeat | Per-command subprocess or raw TCP |
|
||||
| **Reliability** | Auto-reconnect with exponential backoff (1s→60s) | Manual reconnection required |
|
||||
| **Type Safety** | Smart type parsing (Vector2, Color, Rect2, hex colors) | String-only or limited types |
|
||||
| **Error Handling** | Structured errors with codes + suggestions | Generic error messages |
|
||||
| **Undo Support** | All mutations go through UndoRedo system | Direct modifications (no undo) |
|
||||
| **Port Management** | Auto-scan ports 6505-6509 | Fixed port, conflicts possible |
|
||||
|
||||
## License
|
||||
|
||||
Proprietary — see [LICENSE](LICENSE) for details. Purchase includes lifetime updates.
|
||||
@@ -0,0 +1,70 @@
|
||||
# Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
```
|
||||
┌─────────────┐ stdio/MCP ┌──────────────┐ WebSocket:6505 ┌──────────────────┐
|
||||
│ AI Client │ ←────────────────→ │ Node.js MCP │ ←──────────────────→ │ Godot Plugin │
|
||||
│ (Claude Code)│ │ Server │ JSON-RPC 2.0 │ (Editor Plugin) │
|
||||
└─────────────┘ └──────────────┘ └──────────────────┘
|
||||
```
|
||||
|
||||
## Communication Flow
|
||||
|
||||
1. AI client sends MCP tool call (e.g. `add_node`)
|
||||
2. Node.js server translates to JSON-RPC 2.0 request
|
||||
3. WebSocket sends to Godot plugin
|
||||
4. Plugin's command router dispatches to handler
|
||||
5. Handler executes via Godot Editor API (with UndoRedo)
|
||||
6. Result sent back as JSON-RPC 2.0 response
|
||||
7. Node.js formats as MCP tool result
|
||||
8. AI receives structured response
|
||||
|
||||
## Godot Plugin Structure
|
||||
|
||||
```
|
||||
plugin.gd (EditorPlugin)
|
||||
├── websocket_server.gd (TCP+WebSocket server)
|
||||
├── command_router.gd (dispatch hub)
|
||||
│ ├── project_commands.gd (6 commands)
|
||||
│ ├── scene_commands.gd (8 commands)
|
||||
│ ├── node_commands.gd (8 commands)
|
||||
│ ├── script_commands.gd (6 commands)
|
||||
│ └── editor_commands.gd (5 commands)
|
||||
└── ui/status_panel (connection monitor)
|
||||
```
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
### WebSocket over HTTP
|
||||
- Real-time bidirectional communication
|
||||
- Natural for editor integration (persistent connection)
|
||||
- Heartbeat keeps connection alive
|
||||
|
||||
### JSON-RPC 2.0
|
||||
- Standard protocol with well-defined error codes
|
||||
- Each request has unique ID for tracking
|
||||
- Easy to debug and extend
|
||||
|
||||
### UndoRedo Integration
|
||||
- All scene modifications go through `EditorUndoRedoManager`
|
||||
- Users can Ctrl+Z any AI-made change
|
||||
- Prevents accidental data loss
|
||||
|
||||
### Type Parsing
|
||||
- `PropertyParser` handles string → Godot type conversion
|
||||
- Supports Vector2/3, Color, Rect2, NodePath, etc.
|
||||
- AI can send simple strings, plugin handles the rest
|
||||
|
||||
## Error Codes
|
||||
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| -32700 | Parse error (invalid JSON) |
|
||||
| -32600 | Invalid request |
|
||||
| -32601 | Method not found |
|
||||
| -32602 | Invalid params |
|
||||
| -32603 | Internal error |
|
||||
| -32000 | No scene open |
|
||||
| -32001 | Node/resource not found |
|
||||
| -32002 | Script compilation failed |
|
||||
@@ -0,0 +1,73 @@
|
||||
# Installation Guide
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Godot 4.3+ (tested with 4.6)
|
||||
- Node.js 18+
|
||||
- An MCP-compatible AI client (Claude Code, Claude Desktop, etc.)
|
||||
|
||||
## Step 1: Godot Plugin
|
||||
|
||||
1. Copy the `addons/godot_mcp/` folder into your Godot project
|
||||
2. Open Project → Project Settings → Plugins
|
||||
3. Find "Godot MCP Pro" and click Enable
|
||||
4. You should see "MCP Server" appear in the bottom panel
|
||||
5. The status should show "Waiting for connection..."
|
||||
|
||||
## Step 2: Node.js Server
|
||||
|
||||
```bash
|
||||
cd server
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
This creates the compiled server in `server/build/`.
|
||||
|
||||
## Step 3: MCP Client Configuration
|
||||
|
||||
### Claude Code (.mcp.json)
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"godot-mcp-pro": {
|
||||
"command": "node",
|
||||
"args": ["/absolute/path/to/godot-mcp-pro/server/build/index.js"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Port
|
||||
|
||||
Set the `GODOT_MCP_PORT` environment variable (default: 6505):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"godot-mcp-pro": {
|
||||
"command": "node",
|
||||
"args": ["/absolute/path/to/server/build/index.js"],
|
||||
"env": { "GODOT_MCP_PORT": "6510" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Also update the port in `plugin.gd` (line 3: `const PORT := 6505`).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Plugin doesn't appear
|
||||
- Make sure the `addons/godot_mcp/` directory is inside your Godot project
|
||||
- Check that `plugin.cfg` exists in the directory
|
||||
|
||||
### Connection fails
|
||||
- Verify the Godot editor is running with the plugin enabled
|
||||
- Check the bottom panel "MCP Server" tab for status
|
||||
- Ensure no firewall is blocking localhost port 6505
|
||||
|
||||
### Tools timeout
|
||||
- Commands have a 30-second timeout
|
||||
- Large operations (full filesystem scan) may need the `max_depth` parameter
|
||||
@@ -0,0 +1,406 @@
|
||||
# Tools Reference
|
||||
|
||||
## Project Tools
|
||||
|
||||
### get_project_info
|
||||
Returns project metadata including name, Godot version, viewport settings, renderer, and autoloads.
|
||||
|
||||
### get_filesystem_tree
|
||||
Scans the project directory and returns a tree structure.
|
||||
- `path` (optional): Root path (default: `res://`)
|
||||
- `filter` (optional): Glob pattern like `*.gd`, `*.tscn`
|
||||
- `max_depth` (optional): Maximum recursion depth (default: 10)
|
||||
|
||||
### search_files
|
||||
Fuzzy search for files by name.
|
||||
- `query` (required): Search string or glob pattern
|
||||
- `path` (optional): Root path
|
||||
- `file_type` (optional): Extension filter (`gd`, `tscn`, etc.)
|
||||
- `max_results` (optional): Limit results (default: 50)
|
||||
|
||||
### get_project_settings
|
||||
Read settings from project.godot.
|
||||
- `section` (optional): Filter by section prefix (e.g. `display/window`)
|
||||
- `key` (optional): Get a specific setting
|
||||
|
||||
### uid_to_project_path / project_path_to_uid
|
||||
Convert between UIDs (`uid://...`) and resource paths (`res://...`).
|
||||
|
||||
## Scene Tools
|
||||
|
||||
### get_scene_tree
|
||||
Returns the live node hierarchy of the currently edited scene.
|
||||
- `max_depth` (optional): Limit tree depth
|
||||
|
||||
### get_scene_file_content
|
||||
Reads the raw .tscn file.
|
||||
- `path` (required): Scene file path
|
||||
|
||||
### create_scene
|
||||
Creates a new scene file.
|
||||
- `path` (required): Where to save
|
||||
- `root_type` (optional): Root node type (default: `Node2D`)
|
||||
- `root_name` (optional): Root node name
|
||||
|
||||
### open_scene / delete_scene
|
||||
Open or delete a scene file by path.
|
||||
|
||||
### add_scene_instance
|
||||
Instance a scene as a child node.
|
||||
- `scene_path` (required): Scene to instance
|
||||
- `parent_path` (optional): Parent node (default: root)
|
||||
- `name` (optional): Instance name
|
||||
|
||||
### play_scene / stop_scene
|
||||
Run or stop scenes. `play_scene` accepts `mode`: `main`, `current`, or a file path.
|
||||
|
||||
## Node Tools
|
||||
|
||||
### add_node
|
||||
Add a node to the scene.
|
||||
- `type` (required): Node class name
|
||||
- `parent_path` (optional): Parent node path
|
||||
- `name` (optional): Node name
|
||||
- `properties` (optional): Dict of property values
|
||||
|
||||
### delete_node / duplicate_node / move_node
|
||||
Modify scene tree structure. All support undo.
|
||||
|
||||
### update_property
|
||||
Set any node property. Values are auto-parsed:
|
||||
- `"Vector2(100, 200)"` → Vector2
|
||||
- `"#ff0000"` or `"Color(1, 0, 0)"` → Color
|
||||
- `"true"` / `"false"` → bool
|
||||
- Numbers → int/float
|
||||
|
||||
### get_node_properties
|
||||
Get all editor-visible properties of a node.
|
||||
- `category` (optional): Filter prefix
|
||||
|
||||
### add_resource
|
||||
Create and assign a resource to a node property.
|
||||
- `resource_type`: Class name (e.g. `RectangleShape2D`)
|
||||
- `resource_properties` (optional): Properties for the resource
|
||||
|
||||
### set_anchor_preset
|
||||
Set anchor preset on Control nodes. Available presets: `top_left`, `center`, `full_rect`, etc.
|
||||
|
||||
### rename_node
|
||||
Rename a node in the current scene.
|
||||
- `node_path` (required): Path to the node
|
||||
- `new_name` (required): New name for the node
|
||||
|
||||
### connect_signal
|
||||
Connect a signal from one node to a method on another node.
|
||||
- `source_path` (required): Path to the source node (emitter)
|
||||
- `signal_name` (required): Signal name to connect
|
||||
- `target_path` (required): Path to the target node (receiver)
|
||||
- `method_name` (required): Method name on target to call
|
||||
|
||||
### disconnect_signal
|
||||
Disconnect a signal connection between two nodes.
|
||||
- `source_path` (required): Path to the source node (emitter)
|
||||
- `signal_name` (required): Signal name to disconnect
|
||||
- `target_path` (required): Path to the target node (receiver)
|
||||
- `method_name` (required): Method name on target
|
||||
|
||||
## Script Tools
|
||||
|
||||
### list_scripts
|
||||
Find all scripts with class/extends info.
|
||||
|
||||
### read_script / create_script
|
||||
Read or create script files.
|
||||
|
||||
### edit_script
|
||||
Edit scripts via:
|
||||
1. `replacements`: Array of `{search, replace, regex?}` operations
|
||||
2. `content`: Full file replacement
|
||||
3. `insert_at_line` + `text`: Insert at specific line
|
||||
|
||||
### attach_script
|
||||
Attach a script to a node in the current scene.
|
||||
|
||||
### get_open_scripts
|
||||
List scripts currently open in the script editor.
|
||||
|
||||
## Editor Tools
|
||||
|
||||
### get_editor_errors
|
||||
Get recent errors from the Godot log.
|
||||
|
||||
### get_editor_screenshot / get_game_screenshot
|
||||
Capture viewport as PNG (returned as base64 image).
|
||||
|
||||
### execute_editor_script
|
||||
Run arbitrary GDScript in the editor context. Use `_mcp_print(value)` to capture output.
|
||||
|
||||
### clear_output
|
||||
Clear the editor output panel.
|
||||
|
||||
### get_signals
|
||||
Get all signals of a node, including current connections.
|
||||
- `node_path` (required): Path to the node to inspect
|
||||
|
||||
### reload_plugin
|
||||
Reload the Godot MCP Pro plugin (disable/re-enable). Connection will briefly drop and auto-reconnect.
|
||||
|
||||
### reload_project
|
||||
Rescan the Godot project filesystem and reload changed scripts. No reconnection needed.
|
||||
|
||||
### save_scene
|
||||
Save the currently edited scene to disk.
|
||||
- `path` (optional): Path to save to (defaults to current scene path)
|
||||
|
||||
### set_project_setting
|
||||
Set a project setting value via the editor API.
|
||||
- `key` (required): Setting key (e.g. `display/window/size/viewport_width`)
|
||||
- `value` (required): Value to set (auto-parsed for Vector2, bool, int, float)
|
||||
|
||||
## Input Tools
|
||||
|
||||
### simulate_key
|
||||
Simulate a keyboard key press/release in the running game.
|
||||
- `keycode` (required): Key constant (e.g. `KEY_SPACE`, `KEY_W`)
|
||||
- `pressed` (optional): true for press, false for release
|
||||
- `ctrl`, `shift`, `alt` (optional): Modifier keys
|
||||
|
||||
### simulate_mouse_click
|
||||
Simulate a mouse button click at a position in the running game.
|
||||
- `x`, `y` (optional): Viewport position
|
||||
- `button` (optional): 1=left, 2=right, 3=middle
|
||||
- `pressed` (optional): true for press, false for release
|
||||
|
||||
### simulate_mouse_move
|
||||
Simulate mouse movement in the running game.
|
||||
- `x`, `y` (optional): Target position
|
||||
- `relative_x`, `relative_y` (optional): Relative movement
|
||||
|
||||
### simulate_action
|
||||
Simulate a Godot Input Action in the running game.
|
||||
- `action` (required): Action name from Input Map
|
||||
- `pressed` (optional): true for press, false for release
|
||||
- `strength` (optional): 0.0–1.0
|
||||
|
||||
### simulate_sequence
|
||||
Simulate a sequence of input events with frame delays.
|
||||
- `events` (required): Array of input events
|
||||
- `frame_delay` (optional): Frames between events
|
||||
|
||||
## Runtime Tools
|
||||
|
||||
### get_game_scene_tree
|
||||
Get the scene tree of the currently running game.
|
||||
- `max_depth` (optional): Maximum tree depth
|
||||
|
||||
### get_game_node_properties
|
||||
Get properties of a node in the running game.
|
||||
- `node_path` (required): Absolute node path
|
||||
- `properties` (optional): Specific property names to read
|
||||
|
||||
### capture_frames
|
||||
Capture multiple screenshots at regular frame intervals from the running game.
|
||||
- `count` (optional): Number of frames (1–30)
|
||||
- `frame_interval` (optional): Frames between captures
|
||||
- `half_resolution` (optional): Halve resolution to reduce data size
|
||||
|
||||
### monitor_properties
|
||||
Record property values over multiple frames from the running game.
|
||||
- `node_path` (required): Absolute node path
|
||||
- `properties` (required): Property names to monitor
|
||||
- `frame_count` (optional): Number of samples (1–600)
|
||||
- `frame_interval` (optional): Frames between samples
|
||||
|
||||
## Animation Tools
|
||||
|
||||
### list_animations
|
||||
List all animations in an AnimationPlayer node.
|
||||
- `node_path` (required): Path to the AnimationPlayer
|
||||
|
||||
### create_animation
|
||||
Create a new animation in an AnimationPlayer.
|
||||
- `node_path` (required): Path to the AnimationPlayer
|
||||
- `name` (required): Animation name
|
||||
- `length` (optional): Length in seconds (default: 1.0)
|
||||
- `loop_mode` (optional): 0=none, 1=linear, 2=pingpong
|
||||
|
||||
### add_animation_track
|
||||
Add a track to an animation.
|
||||
- `node_path` (required): Path to the AnimationPlayer
|
||||
- `animation` (required): Animation name
|
||||
- `track_path` (required): Node path and property (e.g. `Sprite2D:position`)
|
||||
- `track_type` (optional): value, position_2d, rotation_2d, scale_2d, method, bezier, blend_shape
|
||||
- `update_mode` (optional): continuous, discrete, capture
|
||||
|
||||
### set_animation_keyframe
|
||||
Insert a keyframe into an animation track.
|
||||
- `node_path` (required): Path to the AnimationPlayer
|
||||
- `animation` (required): Animation name
|
||||
- `track_index` (required): Track index
|
||||
- `time` (required): Time position in seconds
|
||||
- `value` (required): Keyframe value (auto-parsed)
|
||||
|
||||
### get_animation_info
|
||||
Get detailed info about an animation including all tracks and keyframes.
|
||||
- `node_path` (required): Path to the AnimationPlayer
|
||||
- `animation` (required): Animation name
|
||||
|
||||
### remove_animation
|
||||
Remove an animation from an AnimationPlayer.
|
||||
- `node_path` (required): Path to the AnimationPlayer
|
||||
- `name` (required): Animation name
|
||||
|
||||
## TileMap Tools
|
||||
|
||||
### tilemap_set_cell
|
||||
Set a single cell in a TileMapLayer.
|
||||
- `node_path` (required): Path to the TileMapLayer
|
||||
- `x`, `y` (required): Cell coordinates
|
||||
- `source_id` (optional): Tile source ID
|
||||
- `atlas_x`, `atlas_y` (optional): Atlas coordinates
|
||||
- `alternative` (optional): Alternative tile ID
|
||||
|
||||
### tilemap_fill_rect
|
||||
Fill a rectangular region with tiles.
|
||||
- `node_path` (required): Path to the TileMapLayer
|
||||
- `x1`, `y1`, `x2`, `y2` (required): Rectangle bounds
|
||||
- `source_id`, `atlas_x`, `atlas_y`, `alternative` (optional): Tile data
|
||||
|
||||
### tilemap_get_cell
|
||||
Get tile data at a specific cell.
|
||||
- `node_path` (required): Path to the TileMapLayer
|
||||
- `x`, `y` (required): Cell coordinates
|
||||
|
||||
### tilemap_clear
|
||||
Clear all cells in a TileMapLayer.
|
||||
- `node_path` (required): Path to the TileMapLayer
|
||||
|
||||
### tilemap_get_info
|
||||
Get TileMapLayer info including tile set sources and cell count.
|
||||
- `node_path` (required): Path to the TileMapLayer
|
||||
|
||||
### tilemap_get_used_cells
|
||||
Get a list of used (non-empty) cells.
|
||||
- `node_path` (required): Path to the TileMapLayer
|
||||
- `max_count` (optional): Maximum cells to return (default: 500)
|
||||
|
||||
## Theme Tools
|
||||
|
||||
### create_theme
|
||||
Create a new Theme resource file.
|
||||
- `path` (required): Save path (e.g. `res://themes/main.tres`)
|
||||
- `default_font_size` (optional): Default font size
|
||||
|
||||
### set_theme_color
|
||||
Set a theme color override on a Control node.
|
||||
- `node_path` (required): Path to the Control node
|
||||
- `name` (required): Color name (e.g. `font_color`)
|
||||
- `color` (required): Hex color string
|
||||
|
||||
### set_theme_constant
|
||||
Set a theme constant override on a Control node.
|
||||
- `node_path` (required): Path to the Control node
|
||||
- `name` (required): Constant name
|
||||
- `value` (required): Integer value
|
||||
|
||||
### set_theme_font_size
|
||||
Set a theme font size override on a Control node.
|
||||
- `node_path` (required): Path to the Control node
|
||||
- `name` (required): Font size name (e.g. `font_size`)
|
||||
- `size` (required): Font size in pixels
|
||||
|
||||
### set_theme_stylebox
|
||||
Set a StyleBoxFlat override on a Control node.
|
||||
- `node_path` (required): Path to the Control node
|
||||
- `name` (required): Style name (e.g. `panel`, `normal`)
|
||||
- `bg_color` (optional): Background color
|
||||
- `border_color` (optional): Border color
|
||||
- `border_width` (optional): Border width
|
||||
- `corner_radius` (optional): Corner radius
|
||||
- `padding` (optional): Content padding
|
||||
|
||||
### get_theme_info
|
||||
Get theme information and overrides for a Control node.
|
||||
- `node_path` (required): Path to the Control node
|
||||
|
||||
## Profiling Tools
|
||||
|
||||
### get_performance_monitors
|
||||
Get all Godot performance monitors (FPS, memory, draw calls, physics, navigation).
|
||||
- `category` (optional): Filter by prefix (e.g. `render`, `physics_2d`)
|
||||
|
||||
### get_editor_performance
|
||||
Get a quick performance summary (FPS, frame time, draw calls, memory).
|
||||
|
||||
## Batch & Refactoring Tools
|
||||
|
||||
### find_nodes_by_type
|
||||
Find all nodes of a specific type in the current scene.
|
||||
- `type` (required): Node class name
|
||||
- `recursive` (optional): Search recursively (default: true)
|
||||
|
||||
### find_signal_connections
|
||||
Find all signal connections in the current scene.
|
||||
- `signal_name` (optional): Filter by signal name
|
||||
- `node_path` (optional): Filter by node path
|
||||
|
||||
### batch_set_property
|
||||
Set a property on all nodes of a given type.
|
||||
- `type` (required): Node type to target
|
||||
- `property` (required): Property name
|
||||
- `value` (required): Value to set (auto-parsed)
|
||||
|
||||
### find_node_references
|
||||
Search through project files for a text pattern.
|
||||
- `pattern` (required): Text pattern to search for
|
||||
|
||||
### get_scene_dependencies
|
||||
Get all resource dependencies of a scene or resource file.
|
||||
- `path` (required): Path to the file
|
||||
|
||||
## Shader Tools
|
||||
|
||||
### create_shader
|
||||
Create a new shader file with template or custom content.
|
||||
- `path` (required): Shader file path
|
||||
- `shader_type` (optional): spatial, canvas_item, particles, sky
|
||||
- `content` (optional): Full shader code
|
||||
|
||||
### read_shader
|
||||
Read the content of a shader file.
|
||||
- `path` (required): Path to the shader file
|
||||
|
||||
### edit_shader
|
||||
Edit a shader file using full replacement or search-and-replace.
|
||||
- `path` (required): Path to the shader file
|
||||
- `content` (optional): Full replacement content
|
||||
- `replacements` (optional): Array of `{search, replace}` operations
|
||||
|
||||
### assign_shader_material
|
||||
Create a ShaderMaterial from a shader and assign to a node.
|
||||
- `node_path` (required): Target node path
|
||||
- `shader_path` (required): Path to the shader file
|
||||
|
||||
### set_shader_param
|
||||
Set a shader parameter on a node's ShaderMaterial.
|
||||
- `node_path` (required): Node with ShaderMaterial
|
||||
- `param` (required): Parameter name
|
||||
- `value` (required): Parameter value (auto-parsed)
|
||||
|
||||
### get_shader_params
|
||||
Get all shader parameters from a node's ShaderMaterial.
|
||||
- `node_path` (required): Node with ShaderMaterial
|
||||
|
||||
## Export Tools
|
||||
|
||||
### list_export_presets
|
||||
List all export presets configured in export_presets.cfg.
|
||||
|
||||
### export_project
|
||||
Get the export command for a preset.
|
||||
- `preset_name` (optional): Preset name
|
||||
- `preset_index` (optional): Preset index
|
||||
- `debug` (optional): Debug export (default: true)
|
||||
|
||||
### get_export_info
|
||||
Get export-related project info (executable path, templates directory, project path).
|
||||
@@ -0,0 +1,146 @@
|
||||
# Godot MCP Pro - AI Assistant Instructions
|
||||
|
||||
You have access to the Godot MCP Pro toolset for building and testing Godot games through the editor. Follow these rules carefully.
|
||||
|
||||
## Critical: Editor vs Runtime Tools
|
||||
|
||||
Tools are split into two categories. **Using a runtime tool without starting the game will always fail.**
|
||||
|
||||
### Editor Tools (always available)
|
||||
These work on the currently open scene in the Godot editor:
|
||||
- **Scene**: `get_scene_tree`, `create_scene`, `open_scene`, `save_scene`, `delete_scene`, `add_scene_instance`, `get_scene_file_content`, `get_scene_exports`
|
||||
- **Nodes**: `add_node`, `delete_node`, `duplicate_node`, `move_node`, `rename_node`, `update_property`, `get_node_properties`, `add_resource`, `set_anchor_preset`, `connect_signal`, `disconnect_signal`, `get_node_groups`, `set_node_groups`, `find_nodes_in_group`
|
||||
- **Scripts**: `create_script`, `read_script`, `edit_script`, `validate_script`, `attach_script`, `get_open_scripts`, `list_scripts`
|
||||
- **Project**: `get_project_info`, `get_project_settings`, `set_project_setting`, `get_project_statistics`, `get_filesystem_tree`, `get_input_actions`, `set_input_action`
|
||||
- **Editor**: `execute_editor_script`, `get_editor_errors`, `get_output_log`, `get_editor_screenshot`, `clear_output`, `reload_plugin`, `reload_project`
|
||||
- **Resources**: `create_resource`, `read_resource`, `edit_resource`, `get_resource_preview`
|
||||
- **Batch**: `batch_add_nodes`, `batch_set_property`, `find_nodes_by_type`, `find_signal_connections`, `find_node_references`, `get_scene_dependencies`, `cross_scene_set_property`
|
||||
- **3D**: `add_mesh_instance`, `setup_environment`, `setup_lighting`, `setup_camera_3d`, `setup_collision`, `setup_physics_body`, `set_material_3d`, `add_raycast`, `add_gridmap`
|
||||
- **Animation**: `create_animation`, `add_animation_track`, `set_animation_keyframe`, `list_animations`, `get_animation_info`, `remove_animation`
|
||||
- **Animation Tree**: `create_animation_tree`, `get_animation_tree_structure`, `add_state_machine_state`, `add_state_machine_transition`, `remove_state_machine_state`, `remove_state_machine_transition`, `set_blend_tree_node`, `set_tree_parameter`
|
||||
- **Audio**: `add_audio_player`, `add_audio_bus`, `add_audio_bus_effect`, `set_audio_bus`, `get_audio_bus_layout`, `get_audio_info`
|
||||
- **Navigation**: `setup_navigation_region`, `setup_navigation_agent`, `bake_navigation_mesh`, `set_navigation_layers`, `get_navigation_info`
|
||||
- **Particles**: `create_particles`, `set_particle_material`, `set_particle_color_gradient`, `apply_particle_preset`, `get_particle_info`
|
||||
- **Physics**: `get_physics_layers`, `set_physics_layers`, `get_collision_info`
|
||||
- **Shader**: `create_shader`, `read_shader`, `edit_shader`, `assign_shader_material`, `get_shader_params`, `set_shader_param`
|
||||
- **Theme**: `create_theme`, `get_theme_info`, `set_theme_color`, `set_theme_font_size`, `set_theme_constant`, `set_theme_stylebox`
|
||||
- **Tilemap**: `tilemap_get_info`, `tilemap_set_cell`, `tilemap_get_cell`, `tilemap_fill_rect`, `tilemap_clear`, `tilemap_get_used_cells`
|
||||
- **Export**: `list_export_presets`, `get_export_info`, `export_project`
|
||||
- **Analysis**: `analyze_scene_complexity`, `analyze_signal_flow`, `detect_circular_dependencies`, `find_unused_resources`, `get_performance_monitors`, `search_files`, `search_in_files`, `find_script_references`
|
||||
- **Profiling**: `get_editor_performance`
|
||||
|
||||
### Runtime Tools (require `play_scene` first)
|
||||
You MUST call `play_scene` before using any of these. They interact with the running game:
|
||||
- **Game State**: `get_game_scene_tree`, `get_game_node_properties`, `set_game_node_property`, `execute_game_script`, `get_game_screenshot`, `get_autoload`, `find_nodes_by_script`
|
||||
- **Input Simulation**: `simulate_key`, `simulate_mouse_click`, `simulate_mouse_move`, `simulate_action`, `simulate_sequence`
|
||||
- **Capture/Recording**: `capture_frames`, `record_frames`, `monitor_properties`, `start_recording`, `stop_recording`, `replay_recording`, `batch_get_properties`
|
||||
- **UI Interaction**: `find_ui_elements`, `click_button_by_text`, `wait_for_node`, `find_nearby_nodes`, `navigate_to`, `move_to`
|
||||
- **Testing**: `run_test_scenario`, `assert_node_state`, `assert_screen_text`, `run_stress_test`, `get_test_report`
|
||||
- **Screenshots**: `get_game_screenshot`, `compare_screenshots`
|
||||
- **Control**: `play_scene`, `stop_scene`
|
||||
|
||||
## Workflow Patterns
|
||||
|
||||
### Building a scene from scratch
|
||||
1. `create_scene` or `open_scene`
|
||||
2. Use `add_node` or `batch_add_nodes` to add nodes
|
||||
3. `create_script` + `attach_script` for behavior
|
||||
4. `save_scene`
|
||||
|
||||
### Testing gameplay
|
||||
1. Build scene with editor tools (above)
|
||||
2. `play_scene` to start the game
|
||||
3. Use `simulate_key`/`simulate_mouse_click` for input
|
||||
4. `get_game_screenshot` or `capture_frames` to observe results
|
||||
5. `stop_scene` when done
|
||||
|
||||
### Inspecting a project
|
||||
1. `get_project_info` for overview
|
||||
2. `get_scene_tree` for current scene structure
|
||||
3. `read_script` to read code
|
||||
4. `get_node_properties` for specific node details
|
||||
|
||||
### Migrating code properties to inspector
|
||||
When a script hardcodes visual properties (colors, sizes, positions, theme overrides) that should be in the inspector:
|
||||
1. `read_script` to find hardcoded property assignments (e.g. `modulate = Color(...)`, `add_theme_color_override(...)`)
|
||||
2. `get_node_properties` to see current inspector values
|
||||
3. `update_property` to set the same values as node properties in the inspector
|
||||
4. `edit_script` to remove the hardcoded lines from the script
|
||||
5. `save_scene` to persist the inspector changes
|
||||
6. `validate_script` to verify the script still works
|
||||
|
||||
## Formatting Rules
|
||||
|
||||
### execute_editor_script
|
||||
The `code` parameter must be valid GDScript. Use `_mcp_print(value)` to return output.
|
||||
|
||||
```
|
||||
# Correct
|
||||
_mcp_print("hello")
|
||||
|
||||
# Correct - multi-line
|
||||
var nodes = []
|
||||
for child in EditorInterface.get_edited_scene_root().get_children():
|
||||
nodes.append(child.name)
|
||||
_mcp_print(str(nodes))
|
||||
```
|
||||
|
||||
### execute_game_script
|
||||
Same as above but runs inside the running game. Additional rules:
|
||||
- No nested functions (`func` inside `func` is invalid GDScript)
|
||||
- Use `.get("property")` instead of `.property` for safe access
|
||||
- Runs in a temporary node — use `get_tree()` to access the scene tree
|
||||
|
||||
### batch_add_nodes
|
||||
Pass an array of node definitions. Nodes are processed in order, so earlier nodes can be parents for later ones:
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{"type": "Node2D", "name": "Container", "parent_path": "."},
|
||||
{"type": "Sprite2D", "name": "Icon", "parent_path": "Container"},
|
||||
{"type": "Label", "name": "Title", "parent_path": "Container", "properties": {"text": "Hello"}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Prefer inspector properties over code** — When changing visual properties (colors, sizes, theme overrides, transforms, etc.), use `update_property` to set them directly on the node. This keeps values visible in the Godot inspector and easy to tweak. Only use GDScript when the property isn't available in the inspector or needs to be dynamic at runtime.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
1. **Never edit project.godot directly** — Use `set_project_setting` instead. The Godot editor overwrites the file.
|
||||
2. **GDScript type inference** — Use explicit type annotations in for-loops: `for item: String in array` instead of `for item in array`.
|
||||
3. **Reload after script changes** — After `create_script`, call `reload_project` if the script doesn't take effect.
|
||||
4. **Property values as strings** — Properties like position accept string format: `"Vector2(100, 200)"`, `"Color(1, 0, 0, 1)"`.
|
||||
5. **simulate_key duration** — Use short durations (0.3-0.5s) for precise movement. Integer seconds (1, 2, 3) cause overshooting.
|
||||
6. **compare_screenshots** — Pass file paths (`user://screenshot.png`), not base64 data.
|
||||
|
||||
## CLI Mode (Alternative to MCP Tools)
|
||||
|
||||
If MCP tools are unavailable or you have a terminal/bash tool, you can control Godot via the CLI.
|
||||
The CLI requires the server to be built first (`node build/setup.js install` in the server directory).
|
||||
|
||||
```bash
|
||||
# Discover available command groups
|
||||
node /path/to/server/build/cli.js --help
|
||||
|
||||
# Discover commands in a group
|
||||
node /path/to/server/build/cli.js scene --help
|
||||
|
||||
# Discover options for a specific command
|
||||
node /path/to/server/build/cli.js node add --help
|
||||
|
||||
# Execute commands
|
||||
node /path/to/server/build/cli.js project info
|
||||
node /path/to/server/build/cli.js scene tree
|
||||
node /path/to/server/build/cli.js node add --type CharacterBody3D --name Player --parent /root/Main
|
||||
node /path/to/server/build/cli.js script read --path res://player.gd
|
||||
node /path/to/server/build/cli.js scene play
|
||||
node /path/to/server/build/cli.js input key --key W --duration 0.5
|
||||
node /path/to/server/build/cli.js runtime tree
|
||||
```
|
||||
|
||||
**Command groups**: project, scene, node, script, editor, input, runtime
|
||||
|
||||
Always start by running `--help` to discover available commands. Use the CLI when MCP tools are not loaded or when you need to reduce context usage.
|
||||
@@ -0,0 +1,146 @@
|
||||
# Godot MCP Pro - AI Assistant Instructions
|
||||
|
||||
You have access to the Godot MCP Pro toolset for building and testing Godot games through the editor. Follow these rules carefully.
|
||||
|
||||
## Critical: Editor vs Runtime Tools
|
||||
|
||||
Tools are split into two categories. **Using a runtime tool without starting the game will always fail.**
|
||||
|
||||
### Editor Tools (always available)
|
||||
These work on the currently open scene in the Godot editor:
|
||||
- **Scene**: `get_scene_tree`, `create_scene`, `open_scene`, `save_scene`, `delete_scene`, `add_scene_instance`, `get_scene_file_content`, `get_scene_exports`
|
||||
- **Nodes**: `add_node`, `delete_node`, `duplicate_node`, `move_node`, `rename_node`, `update_property`, `get_node_properties`, `add_resource`, `set_anchor_preset`, `connect_signal`, `disconnect_signal`, `get_node_groups`, `set_node_groups`, `find_nodes_in_group`
|
||||
- **Scripts**: `create_script`, `read_script`, `edit_script`, `validate_script`, `attach_script`, `get_open_scripts`, `list_scripts`
|
||||
- **Project**: `get_project_info`, `get_project_settings`, `set_project_setting`, `get_project_statistics`, `get_filesystem_tree`, `get_input_actions`, `set_input_action`
|
||||
- **Editor**: `execute_editor_script`, `get_editor_errors`, `get_output_log`, `get_editor_screenshot`, `clear_output`, `reload_plugin`, `reload_project`
|
||||
- **Resources**: `create_resource`, `read_resource`, `edit_resource`, `get_resource_preview`
|
||||
- **Batch**: `batch_add_nodes`, `batch_set_property`, `find_nodes_by_type`, `find_signal_connections`, `find_node_references`, `get_scene_dependencies`, `cross_scene_set_property`
|
||||
- **3D**: `add_mesh_instance`, `setup_environment`, `setup_lighting`, `setup_camera_3d`, `setup_collision`, `setup_physics_body`, `set_material_3d`, `add_raycast`, `add_gridmap`
|
||||
- **Animation**: `create_animation`, `add_animation_track`, `set_animation_keyframe`, `list_animations`, `get_animation_info`, `remove_animation`
|
||||
- **Animation Tree**: `create_animation_tree`, `get_animation_tree_structure`, `add_state_machine_state`, `add_state_machine_transition`, `remove_state_machine_state`, `remove_state_machine_transition`, `set_blend_tree_node`, `set_tree_parameter`
|
||||
- **Audio**: `add_audio_player`, `add_audio_bus`, `add_audio_bus_effect`, `set_audio_bus`, `get_audio_bus_layout`, `get_audio_info`
|
||||
- **Navigation**: `setup_navigation_region`, `setup_navigation_agent`, `bake_navigation_mesh`, `set_navigation_layers`, `get_navigation_info`
|
||||
- **Particles**: `create_particles`, `set_particle_material`, `set_particle_color_gradient`, `apply_particle_preset`, `get_particle_info`
|
||||
- **Physics**: `get_physics_layers`, `set_physics_layers`, `get_collision_info`
|
||||
- **Shader**: `create_shader`, `read_shader`, `edit_shader`, `assign_shader_material`, `get_shader_params`, `set_shader_param`
|
||||
- **Theme**: `create_theme`, `get_theme_info`, `set_theme_color`, `set_theme_font_size`, `set_theme_constant`, `set_theme_stylebox`
|
||||
- **Tilemap**: `tilemap_get_info`, `tilemap_set_cell`, `tilemap_get_cell`, `tilemap_fill_rect`, `tilemap_clear`, `tilemap_get_used_cells`
|
||||
- **Export**: `list_export_presets`, `get_export_info`, `export_project`
|
||||
- **Analysis**: `analyze_scene_complexity`, `analyze_signal_flow`, `detect_circular_dependencies`, `find_unused_resources`, `get_performance_monitors`, `search_files`, `search_in_files`, `find_script_references`
|
||||
- **Profiling**: `get_editor_performance`
|
||||
|
||||
### Runtime Tools (require `play_scene` first)
|
||||
You MUST call `play_scene` before using any of these. They interact with the running game:
|
||||
- **Game State**: `get_game_scene_tree`, `get_game_node_properties`, `set_game_node_property`, `execute_game_script`, `get_game_screenshot`, `get_autoload`, `find_nodes_by_script`
|
||||
- **Input Simulation**: `simulate_key`, `simulate_mouse_click`, `simulate_mouse_move`, `simulate_action`, `simulate_sequence`
|
||||
- **Capture/Recording**: `capture_frames`, `record_frames`, `monitor_properties`, `start_recording`, `stop_recording`, `replay_recording`, `batch_get_properties`
|
||||
- **UI Interaction**: `find_ui_elements`, `click_button_by_text`, `wait_for_node`, `find_nearby_nodes`, `navigate_to`, `move_to`
|
||||
- **Testing**: `run_test_scenario`, `assert_node_state`, `assert_screen_text`, `run_stress_test`, `get_test_report`
|
||||
- **Screenshots**: `get_game_screenshot`, `compare_screenshots`
|
||||
- **Control**: `play_scene`, `stop_scene`
|
||||
|
||||
## Workflow Patterns
|
||||
|
||||
### Building a scene from scratch
|
||||
1. `create_scene` or `open_scene`
|
||||
2. Use `add_node` or `batch_add_nodes` to add nodes
|
||||
3. `create_script` + `attach_script` for behavior
|
||||
4. `save_scene`
|
||||
|
||||
### Testing gameplay
|
||||
1. Build scene with editor tools (above)
|
||||
2. `play_scene` to start the game
|
||||
3. Use `simulate_key`/`simulate_mouse_click` for input
|
||||
4. `get_game_screenshot` or `capture_frames` to observe results
|
||||
5. `stop_scene` when done
|
||||
|
||||
### Inspecting a project
|
||||
1. `get_project_info` for overview
|
||||
2. `get_scene_tree` for current scene structure
|
||||
3. `read_script` to read code
|
||||
4. `get_node_properties` for specific node details
|
||||
|
||||
### Migrating code properties to inspector
|
||||
When a script hardcodes visual properties (colors, sizes, positions, theme overrides) that should be in the inspector:
|
||||
1. `read_script` to find hardcoded property assignments (e.g. `modulate = Color(...)`, `add_theme_color_override(...)`)
|
||||
2. `get_node_properties` to see current inspector values
|
||||
3. `update_property` to set the same values as node properties in the inspector
|
||||
4. `edit_script` to remove the hardcoded lines from the script
|
||||
5. `save_scene` to persist the inspector changes
|
||||
6. `validate_script` to verify the script still works
|
||||
|
||||
## Formatting Rules
|
||||
|
||||
### execute_editor_script
|
||||
The `code` parameter must be valid GDScript. Use `_mcp_print(value)` to return output.
|
||||
|
||||
```
|
||||
# Correct
|
||||
_mcp_print("hello")
|
||||
|
||||
# Correct - multi-line
|
||||
var nodes = []
|
||||
for child in EditorInterface.get_edited_scene_root().get_children():
|
||||
nodes.append(child.name)
|
||||
_mcp_print(str(nodes))
|
||||
```
|
||||
|
||||
### execute_game_script
|
||||
Same as above but runs inside the running game. Additional rules:
|
||||
- No nested functions (`func` inside `func` is invalid GDScript)
|
||||
- Use `.get("property")` instead of `.property` for safe access
|
||||
- Runs in a temporary node — use `get_tree()` to access the scene tree
|
||||
|
||||
### batch_add_nodes
|
||||
Pass an array of node definitions. Nodes are processed in order, so earlier nodes can be parents for later ones:
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{"type": "Node2D", "name": "Container", "parent_path": "."},
|
||||
{"type": "Sprite2D", "name": "Icon", "parent_path": "Container"},
|
||||
{"type": "Label", "name": "Title", "parent_path": "Container", "properties": {"text": "Hello"}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Prefer inspector properties over code** — When changing visual properties (colors, sizes, theme overrides, transforms, etc.), use `update_property` to set them directly on the node. This keeps values visible in the Godot inspector and easy to tweak. Only use GDScript when the property isn't available in the inspector or needs to be dynamic at runtime.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
1. **Never edit project.godot directly** — Use `set_project_setting` instead. The Godot editor overwrites the file.
|
||||
2. **GDScript type inference** — Use explicit type annotations in for-loops: `for item: String in array` instead of `for item in array`.
|
||||
3. **Reload after script changes** — After `create_script`, call `reload_project` if the script doesn't take effect.
|
||||
4. **Property values as strings** — Properties like position accept string format: `"Vector2(100, 200)"`, `"Color(1, 0, 0, 1)"`.
|
||||
5. **simulate_key duration** — Use short durations (0.3-0.5s) for precise movement. Integer seconds (1, 2, 3) cause overshooting.
|
||||
6. **compare_screenshots** — Pass file paths (`user://screenshot.png`), not base64 data.
|
||||
|
||||
## CLI Mode (Alternative to MCP Tools)
|
||||
|
||||
If MCP tools are unavailable or you have a terminal/bash tool, you can control Godot via the CLI.
|
||||
The CLI requires the server to be built first (`node build/setup.js install` in the server directory).
|
||||
|
||||
```bash
|
||||
# Discover available command groups
|
||||
node /path/to/server/build/cli.js --help
|
||||
|
||||
# Discover commands in a group
|
||||
node /path/to/server/build/cli.js scene --help
|
||||
|
||||
# Discover options for a specific command
|
||||
node /path/to/server/build/cli.js node add --help
|
||||
|
||||
# Execute commands
|
||||
node /path/to/server/build/cli.js project info
|
||||
node /path/to/server/build/cli.js scene tree
|
||||
node /path/to/server/build/cli.js node add --type CharacterBody3D --name Player --parent /root/Main
|
||||
node /path/to/server/build/cli.js script read --path res://player.gd
|
||||
node /path/to/server/build/cli.js scene play
|
||||
node /path/to/server/build/cli.js input key --key W --duration 0.5
|
||||
node /path/to/server/build/cli.js runtime tree
|
||||
```
|
||||
|
||||
**Command groups**: project, scene, node, script, editor, input, runtime
|
||||
|
||||
Always start by running `--help` to discover available commands. Use the CLI when MCP tools are not loaded or when you need to reduce context usage.
|
||||
@@ -0,0 +1,146 @@
|
||||
# Godot MCP Pro - AI Assistant Instructions
|
||||
|
||||
You have access to the Godot MCP Pro toolset for building and testing Godot games through the editor. Follow these rules carefully.
|
||||
|
||||
## Critical: Editor vs Runtime Tools
|
||||
|
||||
Tools are split into two categories. **Using a runtime tool without starting the game will always fail.**
|
||||
|
||||
### Editor Tools (always available)
|
||||
These work on the currently open scene in the Godot editor:
|
||||
- **Scene**: `get_scene_tree`, `create_scene`, `open_scene`, `save_scene`, `delete_scene`, `add_scene_instance`, `get_scene_file_content`, `get_scene_exports`
|
||||
- **Nodes**: `add_node`, `delete_node`, `duplicate_node`, `move_node`, `rename_node`, `update_property`, `get_node_properties`, `add_resource`, `set_anchor_preset`, `connect_signal`, `disconnect_signal`, `get_node_groups`, `set_node_groups`, `find_nodes_in_group`
|
||||
- **Scripts**: `create_script`, `read_script`, `edit_script`, `validate_script`, `attach_script`, `get_open_scripts`, `list_scripts`
|
||||
- **Project**: `get_project_info`, `get_project_settings`, `set_project_setting`, `get_project_statistics`, `get_filesystem_tree`, `get_input_actions`, `set_input_action`
|
||||
- **Editor**: `execute_editor_script`, `get_editor_errors`, `get_output_log`, `get_editor_screenshot`, `clear_output`, `reload_plugin`, `reload_project`
|
||||
- **Resources**: `create_resource`, `read_resource`, `edit_resource`, `get_resource_preview`
|
||||
- **Batch**: `batch_add_nodes`, `batch_set_property`, `find_nodes_by_type`, `find_signal_connections`, `find_node_references`, `get_scene_dependencies`, `cross_scene_set_property`
|
||||
- **3D**: `add_mesh_instance`, `setup_environment`, `setup_lighting`, `setup_camera_3d`, `setup_collision`, `setup_physics_body`, `set_material_3d`, `add_raycast`, `add_gridmap`
|
||||
- **Animation**: `create_animation`, `add_animation_track`, `set_animation_keyframe`, `list_animations`, `get_animation_info`, `remove_animation`
|
||||
- **Animation Tree**: `create_animation_tree`, `get_animation_tree_structure`, `add_state_machine_state`, `add_state_machine_transition`, `remove_state_machine_state`, `remove_state_machine_transition`, `set_blend_tree_node`, `set_tree_parameter`
|
||||
- **Audio**: `add_audio_player`, `add_audio_bus`, `add_audio_bus_effect`, `set_audio_bus`, `get_audio_bus_layout`, `get_audio_info`
|
||||
- **Navigation**: `setup_navigation_region`, `setup_navigation_agent`, `bake_navigation_mesh`, `set_navigation_layers`, `get_navigation_info`
|
||||
- **Particles**: `create_particles`, `set_particle_material`, `set_particle_color_gradient`, `apply_particle_preset`, `get_particle_info`
|
||||
- **Physics**: `get_physics_layers`, `set_physics_layers`, `get_collision_info`
|
||||
- **Shader**: `create_shader`, `read_shader`, `edit_shader`, `assign_shader_material`, `get_shader_params`, `set_shader_param`
|
||||
- **Theme**: `create_theme`, `get_theme_info`, `set_theme_color`, `set_theme_font_size`, `set_theme_constant`, `set_theme_stylebox`
|
||||
- **Tilemap**: `tilemap_get_info`, `tilemap_set_cell`, `tilemap_get_cell`, `tilemap_fill_rect`, `tilemap_clear`, `tilemap_get_used_cells`
|
||||
- **Export**: `list_export_presets`, `get_export_info`, `export_project`
|
||||
- **Analysis**: `analyze_scene_complexity`, `analyze_signal_flow`, `detect_circular_dependencies`, `find_unused_resources`, `get_performance_monitors`, `search_files`, `search_in_files`, `find_script_references`
|
||||
- **Profiling**: `get_editor_performance`
|
||||
|
||||
### Runtime Tools (require `play_scene` first)
|
||||
You MUST call `play_scene` before using any of these. They interact with the running game:
|
||||
- **Game State**: `get_game_scene_tree`, `get_game_node_properties`, `set_game_node_property`, `execute_game_script`, `get_game_screenshot`, `get_autoload`, `find_nodes_by_script`
|
||||
- **Input Simulation**: `simulate_key`, `simulate_mouse_click`, `simulate_mouse_move`, `simulate_action`, `simulate_sequence`
|
||||
- **Capture/Recording**: `capture_frames`, `record_frames`, `monitor_properties`, `start_recording`, `stop_recording`, `replay_recording`, `batch_get_properties`
|
||||
- **UI Interaction**: `find_ui_elements`, `click_button_by_text`, `wait_for_node`, `find_nearby_nodes`, `navigate_to`, `move_to`
|
||||
- **Testing**: `run_test_scenario`, `assert_node_state`, `assert_screen_text`, `run_stress_test`, `get_test_report`
|
||||
- **Screenshots**: `get_game_screenshot`, `compare_screenshots`
|
||||
- **Control**: `play_scene`, `stop_scene`
|
||||
|
||||
## Workflow Patterns
|
||||
|
||||
### Building a scene from scratch
|
||||
1. `create_scene` or `open_scene`
|
||||
2. Use `add_node` or `batch_add_nodes` to add nodes
|
||||
3. `create_script` + `attach_script` for behavior
|
||||
4. `save_scene`
|
||||
|
||||
### Testing gameplay
|
||||
1. Build scene with editor tools (above)
|
||||
2. `play_scene` to start the game
|
||||
3. Use `simulate_key`/`simulate_mouse_click` for input
|
||||
4. `get_game_screenshot` or `capture_frames` to observe results
|
||||
5. `stop_scene` when done
|
||||
|
||||
### Inspecting a project
|
||||
1. `get_project_info` for overview
|
||||
2. `get_scene_tree` for current scene structure
|
||||
3. `read_script` to read code
|
||||
4. `get_node_properties` for specific node details
|
||||
|
||||
### Migrating code properties to inspector
|
||||
When a script hardcodes visual properties (colors, sizes, positions, theme overrides) that should be in the inspector:
|
||||
1. `read_script` to find hardcoded property assignments (e.g. `modulate = Color(...)`, `add_theme_color_override(...)`)
|
||||
2. `get_node_properties` to see current inspector values
|
||||
3. `update_property` to set the same values as node properties in the inspector
|
||||
4. `edit_script` to remove the hardcoded lines from the script
|
||||
5. `save_scene` to persist the inspector changes
|
||||
6. `validate_script` to verify the script still works
|
||||
|
||||
## Formatting Rules
|
||||
|
||||
### execute_editor_script
|
||||
The `code` parameter must be valid GDScript. Use `_mcp_print(value)` to return output.
|
||||
|
||||
```
|
||||
# Correct
|
||||
_mcp_print("hello")
|
||||
|
||||
# Correct - multi-line
|
||||
var nodes = []
|
||||
for child in EditorInterface.get_edited_scene_root().get_children():
|
||||
nodes.append(child.name)
|
||||
_mcp_print(str(nodes))
|
||||
```
|
||||
|
||||
### execute_game_script
|
||||
Same as above but runs inside the running game. Additional rules:
|
||||
- No nested functions (`func` inside `func` is invalid GDScript)
|
||||
- Use `.get("property")` instead of `.property` for safe access
|
||||
- Runs in a temporary node — use `get_tree()` to access the scene tree
|
||||
|
||||
### batch_add_nodes
|
||||
Pass an array of node definitions. Nodes are processed in order, so earlier nodes can be parents for later ones:
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{"type": "Node2D", "name": "Container", "parent_path": "."},
|
||||
{"type": "Sprite2D", "name": "Icon", "parent_path": "Container"},
|
||||
{"type": "Label", "name": "Title", "parent_path": "Container", "properties": {"text": "Hello"}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Prefer inspector properties over code** — When changing visual properties (colors, sizes, theme overrides, transforms, etc.), use `update_property` to set them directly on the node. This keeps values visible in the Godot inspector and easy to tweak. Only use GDScript when the property isn't available in the inspector or needs to be dynamic at runtime.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
1. **Never edit project.godot directly** — Use `set_project_setting` instead. The Godot editor overwrites the file.
|
||||
2. **GDScript type inference** — Use explicit type annotations in for-loops: `for item: String in array` instead of `for item in array`.
|
||||
3. **Reload after script changes** — After `create_script`, call `reload_project` if the script doesn't take effect.
|
||||
4. **Property values as strings** — Properties like position accept string format: `"Vector2(100, 200)"`, `"Color(1, 0, 0, 1)"`.
|
||||
5. **simulate_key duration** — Use short durations (0.3-0.5s) for precise movement. Integer seconds (1, 2, 3) cause overshooting.
|
||||
6. **compare_screenshots** — Pass file paths (`user://screenshot.png`), not base64 data.
|
||||
|
||||
## CLI Mode (Alternative to MCP Tools)
|
||||
|
||||
If MCP tools are unavailable or you have a terminal/bash tool, you can control Godot via the CLI.
|
||||
The CLI requires the server to be built first (`node build/setup.js install` in the server directory).
|
||||
|
||||
```bash
|
||||
# Discover available command groups
|
||||
node /path/to/server/build/cli.js --help
|
||||
|
||||
# Discover commands in a group
|
||||
node /path/to/server/build/cli.js scene --help
|
||||
|
||||
# Discover options for a specific command
|
||||
node /path/to/server/build/cli.js node add --help
|
||||
|
||||
# Execute commands
|
||||
node /path/to/server/build/cli.js project info
|
||||
node /path/to/server/build/cli.js scene tree
|
||||
node /path/to/server/build/cli.js node add --type CharacterBody3D --name Player --parent /root/Main
|
||||
node /path/to/server/build/cli.js script read --path res://player.gd
|
||||
node /path/to/server/build/cli.js scene play
|
||||
node /path/to/server/build/cli.js input key --key W --duration 0.5
|
||||
node /path/to/server/build/cli.js runtime tree
|
||||
```
|
||||
|
||||
**Command groups**: project, scene, node, script, editor, input, runtime
|
||||
|
||||
Always start by running `--help` to discover available commands. Use the CLI when MCP tools are not loaded or when you need to reduce context usage.
|
||||
@@ -0,0 +1,146 @@
|
||||
# Godot MCP Pro - AI Assistant Instructions
|
||||
|
||||
You have access to the Godot MCP Pro toolset for building and testing Godot games through the editor. Follow these rules carefully.
|
||||
|
||||
## Critical: Editor vs Runtime Tools
|
||||
|
||||
Tools are split into two categories. **Using a runtime tool without starting the game will always fail.**
|
||||
|
||||
### Editor Tools (always available)
|
||||
These work on the currently open scene in the Godot editor:
|
||||
- **Scene**: `get_scene_tree`, `create_scene`, `open_scene`, `save_scene`, `delete_scene`, `add_scene_instance`, `get_scene_file_content`, `get_scene_exports`
|
||||
- **Nodes**: `add_node`, `delete_node`, `duplicate_node`, `move_node`, `rename_node`, `update_property`, `get_node_properties`, `add_resource`, `set_anchor_preset`, `connect_signal`, `disconnect_signal`, `get_node_groups`, `set_node_groups`, `find_nodes_in_group`
|
||||
- **Scripts**: `create_script`, `read_script`, `edit_script`, `validate_script`, `attach_script`, `get_open_scripts`, `list_scripts`
|
||||
- **Project**: `get_project_info`, `get_project_settings`, `set_project_setting`, `get_project_statistics`, `get_filesystem_tree`, `get_input_actions`, `set_input_action`
|
||||
- **Editor**: `execute_editor_script`, `get_editor_errors`, `get_output_log`, `get_editor_screenshot`, `clear_output`, `reload_plugin`, `reload_project`
|
||||
- **Resources**: `create_resource`, `read_resource`, `edit_resource`, `get_resource_preview`
|
||||
- **Batch**: `batch_add_nodes`, `batch_set_property`, `find_nodes_by_type`, `find_signal_connections`, `find_node_references`, `get_scene_dependencies`, `cross_scene_set_property`
|
||||
- **3D**: `add_mesh_instance`, `setup_environment`, `setup_lighting`, `setup_camera_3d`, `setup_collision`, `setup_physics_body`, `set_material_3d`, `add_raycast`, `add_gridmap`
|
||||
- **Animation**: `create_animation`, `add_animation_track`, `set_animation_keyframe`, `list_animations`, `get_animation_info`, `remove_animation`
|
||||
- **Animation Tree**: `create_animation_tree`, `get_animation_tree_structure`, `add_state_machine_state`, `add_state_machine_transition`, `remove_state_machine_state`, `remove_state_machine_transition`, `set_blend_tree_node`, `set_tree_parameter`
|
||||
- **Audio**: `add_audio_player`, `add_audio_bus`, `add_audio_bus_effect`, `set_audio_bus`, `get_audio_bus_layout`, `get_audio_info`
|
||||
- **Navigation**: `setup_navigation_region`, `setup_navigation_agent`, `bake_navigation_mesh`, `set_navigation_layers`, `get_navigation_info`
|
||||
- **Particles**: `create_particles`, `set_particle_material`, `set_particle_color_gradient`, `apply_particle_preset`, `get_particle_info`
|
||||
- **Physics**: `get_physics_layers`, `set_physics_layers`, `get_collision_info`
|
||||
- **Shader**: `create_shader`, `read_shader`, `edit_shader`, `assign_shader_material`, `get_shader_params`, `set_shader_param`
|
||||
- **Theme**: `create_theme`, `get_theme_info`, `set_theme_color`, `set_theme_font_size`, `set_theme_constant`, `set_theme_stylebox`
|
||||
- **Tilemap**: `tilemap_get_info`, `tilemap_set_cell`, `tilemap_get_cell`, `tilemap_fill_rect`, `tilemap_clear`, `tilemap_get_used_cells`
|
||||
- **Export**: `list_export_presets`, `get_export_info`, `export_project`
|
||||
- **Analysis**: `analyze_scene_complexity`, `analyze_signal_flow`, `detect_circular_dependencies`, `find_unused_resources`, `get_performance_monitors`, `search_files`, `search_in_files`, `find_script_references`
|
||||
- **Profiling**: `get_editor_performance`
|
||||
|
||||
### Runtime Tools (require `play_scene` first)
|
||||
You MUST call `play_scene` before using any of these. They interact with the running game:
|
||||
- **Game State**: `get_game_scene_tree`, `get_game_node_properties`, `set_game_node_property`, `execute_game_script`, `get_game_screenshot`, `get_autoload`, `find_nodes_by_script`
|
||||
- **Input Simulation**: `simulate_key`, `simulate_mouse_click`, `simulate_mouse_move`, `simulate_action`, `simulate_sequence`
|
||||
- **Capture/Recording**: `capture_frames`, `record_frames`, `monitor_properties`, `start_recording`, `stop_recording`, `replay_recording`, `batch_get_properties`
|
||||
- **UI Interaction**: `find_ui_elements`, `click_button_by_text`, `wait_for_node`, `find_nearby_nodes`, `navigate_to`, `move_to`
|
||||
- **Testing**: `run_test_scenario`, `assert_node_state`, `assert_screen_text`, `run_stress_test`, `get_test_report`
|
||||
- **Screenshots**: `get_game_screenshot`, `compare_screenshots`
|
||||
- **Control**: `play_scene`, `stop_scene`
|
||||
|
||||
## Workflow Patterns
|
||||
|
||||
### Building a scene from scratch
|
||||
1. `create_scene` or `open_scene`
|
||||
2. Use `add_node` or `batch_add_nodes` to add nodes
|
||||
3. `create_script` + `attach_script` for behavior
|
||||
4. `save_scene`
|
||||
|
||||
### Testing gameplay
|
||||
1. Build scene with editor tools (above)
|
||||
2. `play_scene` to start the game
|
||||
3. Use `simulate_key`/`simulate_mouse_click` for input
|
||||
4. `get_game_screenshot` or `capture_frames` to observe results
|
||||
5. `stop_scene` when done
|
||||
|
||||
### Inspecting a project
|
||||
1. `get_project_info` for overview
|
||||
2. `get_scene_tree` for current scene structure
|
||||
3. `read_script` to read code
|
||||
4. `get_node_properties` for specific node details
|
||||
|
||||
### Migrating code properties to inspector
|
||||
When a script hardcodes visual properties (colors, sizes, positions, theme overrides) that should be in the inspector:
|
||||
1. `read_script` to find hardcoded property assignments (e.g. `modulate = Color(...)`, `add_theme_color_override(...)`)
|
||||
2. `get_node_properties` to see current inspector values
|
||||
3. `update_property` to set the same values as node properties in the inspector
|
||||
4. `edit_script` to remove the hardcoded lines from the script
|
||||
5. `save_scene` to persist the inspector changes
|
||||
6. `validate_script` to verify the script still works
|
||||
|
||||
## Formatting Rules
|
||||
|
||||
### execute_editor_script
|
||||
The `code` parameter must be valid GDScript. Use `_mcp_print(value)` to return output.
|
||||
|
||||
```
|
||||
# Correct
|
||||
_mcp_print("hello")
|
||||
|
||||
# Correct - multi-line
|
||||
var nodes = []
|
||||
for child in EditorInterface.get_edited_scene_root().get_children():
|
||||
nodes.append(child.name)
|
||||
_mcp_print(str(nodes))
|
||||
```
|
||||
|
||||
### execute_game_script
|
||||
Same as above but runs inside the running game. Additional rules:
|
||||
- No nested functions (`func` inside `func` is invalid GDScript)
|
||||
- Use `.get("property")` instead of `.property` for safe access
|
||||
- Runs in a temporary node — use `get_tree()` to access the scene tree
|
||||
|
||||
### batch_add_nodes
|
||||
Pass an array of node definitions. Nodes are processed in order, so earlier nodes can be parents for later ones:
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{"type": "Node2D", "name": "Container", "parent_path": "."},
|
||||
{"type": "Sprite2D", "name": "Icon", "parent_path": "Container"},
|
||||
{"type": "Label", "name": "Title", "parent_path": "Container", "properties": {"text": "Hello"}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Prefer inspector properties over code** — When changing visual properties (colors, sizes, theme overrides, transforms, etc.), use `update_property` to set them directly on the node. This keeps values visible in the Godot inspector and easy to tweak. Only use GDScript when the property isn't available in the inspector or needs to be dynamic at runtime.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
1. **Never edit project.godot directly** — Use `set_project_setting` instead. The Godot editor overwrites the file.
|
||||
2. **GDScript type inference** — Use explicit type annotations in for-loops: `for item: String in array` instead of `for item in array`.
|
||||
3. **Reload after script changes** — After `create_script`, call `reload_project` if the script doesn't take effect.
|
||||
4. **Property values as strings** — Properties like position accept string format: `"Vector2(100, 200)"`, `"Color(1, 0, 0, 1)"`.
|
||||
5. **simulate_key duration** — Use short durations (0.3-0.5s) for precise movement. Integer seconds (1, 2, 3) cause overshooting.
|
||||
6. **compare_screenshots** — Pass file paths (`user://screenshot.png`), not base64 data.
|
||||
|
||||
## CLI Mode (Alternative to MCP Tools)
|
||||
|
||||
If MCP tools are unavailable or you have a terminal/bash tool, you can control Godot via the CLI.
|
||||
The CLI requires the server to be built first (`node build/setup.js install` in the server directory).
|
||||
|
||||
```bash
|
||||
# Discover available command groups
|
||||
node /path/to/server/build/cli.js --help
|
||||
|
||||
# Discover commands in a group
|
||||
node /path/to/server/build/cli.js scene --help
|
||||
|
||||
# Discover options for a specific command
|
||||
node /path/to/server/build/cli.js node add --help
|
||||
|
||||
# Execute commands
|
||||
node /path/to/server/build/cli.js project info
|
||||
node /path/to/server/build/cli.js scene tree
|
||||
node /path/to/server/build/cli.js node add --type CharacterBody3D --name Player --parent /root/Main
|
||||
node /path/to/server/build/cli.js script read --path res://player.gd
|
||||
node /path/to/server/build/cli.js scene play
|
||||
node /path/to/server/build/cli.js input key --key W --duration 0.5
|
||||
node /path/to/server/build/cli.js runtime tree
|
||||
```
|
||||
|
||||
**Command groups**: project, scene, node, script, editor, input, runtime
|
||||
|
||||
Always start by running `--help` to discover available commands. Use the CLI when MCP tools are not loaded or when you need to reduce context usage.
|
||||
@@ -0,0 +1,146 @@
|
||||
# Godot MCP Pro - AI Assistant Instructions
|
||||
|
||||
You have access to the Godot MCP Pro toolset for building and testing Godot games through the editor. Follow these rules carefully.
|
||||
|
||||
## Critical: Editor vs Runtime Tools
|
||||
|
||||
Tools are split into two categories. **Using a runtime tool without starting the game will always fail.**
|
||||
|
||||
### Editor Tools (always available)
|
||||
These work on the currently open scene in the Godot editor:
|
||||
- **Scene**: `get_scene_tree`, `create_scene`, `open_scene`, `save_scene`, `delete_scene`, `add_scene_instance`, `get_scene_file_content`, `get_scene_exports`
|
||||
- **Nodes**: `add_node`, `delete_node`, `duplicate_node`, `move_node`, `rename_node`, `update_property`, `get_node_properties`, `add_resource`, `set_anchor_preset`, `connect_signal`, `disconnect_signal`, `get_node_groups`, `set_node_groups`, `find_nodes_in_group`
|
||||
- **Scripts**: `create_script`, `read_script`, `edit_script`, `validate_script`, `attach_script`, `get_open_scripts`, `list_scripts`
|
||||
- **Project**: `get_project_info`, `get_project_settings`, `set_project_setting`, `get_project_statistics`, `get_filesystem_tree`, `get_input_actions`, `set_input_action`
|
||||
- **Editor**: `execute_editor_script`, `get_editor_errors`, `get_output_log`, `get_editor_screenshot`, `clear_output`, `reload_plugin`, `reload_project`
|
||||
- **Resources**: `create_resource`, `read_resource`, `edit_resource`, `get_resource_preview`
|
||||
- **Batch**: `batch_add_nodes`, `batch_set_property`, `find_nodes_by_type`, `find_signal_connections`, `find_node_references`, `get_scene_dependencies`, `cross_scene_set_property`
|
||||
- **3D**: `add_mesh_instance`, `setup_environment`, `setup_lighting`, `setup_camera_3d`, `setup_collision`, `setup_physics_body`, `set_material_3d`, `add_raycast`, `add_gridmap`
|
||||
- **Animation**: `create_animation`, `add_animation_track`, `set_animation_keyframe`, `list_animations`, `get_animation_info`, `remove_animation`
|
||||
- **Animation Tree**: `create_animation_tree`, `get_animation_tree_structure`, `add_state_machine_state`, `add_state_machine_transition`, `remove_state_machine_state`, `remove_state_machine_transition`, `set_blend_tree_node`, `set_tree_parameter`
|
||||
- **Audio**: `add_audio_player`, `add_audio_bus`, `add_audio_bus_effect`, `set_audio_bus`, `get_audio_bus_layout`, `get_audio_info`
|
||||
- **Navigation**: `setup_navigation_region`, `setup_navigation_agent`, `bake_navigation_mesh`, `set_navigation_layers`, `get_navigation_info`
|
||||
- **Particles**: `create_particles`, `set_particle_material`, `set_particle_color_gradient`, `apply_particle_preset`, `get_particle_info`
|
||||
- **Physics**: `get_physics_layers`, `set_physics_layers`, `get_collision_info`
|
||||
- **Shader**: `create_shader`, `read_shader`, `edit_shader`, `assign_shader_material`, `get_shader_params`, `set_shader_param`
|
||||
- **Theme**: `create_theme`, `get_theme_info`, `set_theme_color`, `set_theme_font_size`, `set_theme_constant`, `set_theme_stylebox`
|
||||
- **Tilemap**: `tilemap_get_info`, `tilemap_set_cell`, `tilemap_get_cell`, `tilemap_fill_rect`, `tilemap_clear`, `tilemap_get_used_cells`
|
||||
- **Export**: `list_export_presets`, `get_export_info`, `export_project`
|
||||
- **Analysis**: `analyze_scene_complexity`, `analyze_signal_flow`, `detect_circular_dependencies`, `find_unused_resources`, `get_performance_monitors`, `search_files`, `search_in_files`, `find_script_references`
|
||||
- **Profiling**: `get_editor_performance`
|
||||
|
||||
### Runtime Tools (require `play_scene` first)
|
||||
You MUST call `play_scene` before using any of these. They interact with the running game:
|
||||
- **Game State**: `get_game_scene_tree`, `get_game_node_properties`, `set_game_node_property`, `execute_game_script`, `get_game_screenshot`, `get_autoload`, `find_nodes_by_script`
|
||||
- **Input Simulation**: `simulate_key`, `simulate_mouse_click`, `simulate_mouse_move`, `simulate_action`, `simulate_sequence`
|
||||
- **Capture/Recording**: `capture_frames`, `record_frames`, `monitor_properties`, `start_recording`, `stop_recording`, `replay_recording`, `batch_get_properties`
|
||||
- **UI Interaction**: `find_ui_elements`, `click_button_by_text`, `wait_for_node`, `find_nearby_nodes`, `navigate_to`, `move_to`
|
||||
- **Testing**: `run_test_scenario`, `assert_node_state`, `assert_screen_text`, `run_stress_test`, `get_test_report`
|
||||
- **Screenshots**: `get_game_screenshot`, `compare_screenshots`
|
||||
- **Control**: `play_scene`, `stop_scene`
|
||||
|
||||
## Workflow Patterns
|
||||
|
||||
### Building a scene from scratch
|
||||
1. `create_scene` or `open_scene`
|
||||
2. Use `add_node` or `batch_add_nodes` to add nodes
|
||||
3. `create_script` + `attach_script` for behavior
|
||||
4. `save_scene`
|
||||
|
||||
### Testing gameplay
|
||||
1. Build scene with editor tools (above)
|
||||
2. `play_scene` to start the game
|
||||
3. Use `simulate_key`/`simulate_mouse_click` for input
|
||||
4. `get_game_screenshot` or `capture_frames` to observe results
|
||||
5. `stop_scene` when done
|
||||
|
||||
### Inspecting a project
|
||||
1. `get_project_info` for overview
|
||||
2. `get_scene_tree` for current scene structure
|
||||
3. `read_script` to read code
|
||||
4. `get_node_properties` for specific node details
|
||||
|
||||
### Migrating code properties to inspector
|
||||
When a script hardcodes visual properties (colors, sizes, positions, theme overrides) that should be in the inspector:
|
||||
1. `read_script` to find hardcoded property assignments (e.g. `modulate = Color(...)`, `add_theme_color_override(...)`)
|
||||
2. `get_node_properties` to see current inspector values
|
||||
3. `update_property` to set the same values as node properties in the inspector
|
||||
4. `edit_script` to remove the hardcoded lines from the script
|
||||
5. `save_scene` to persist the inspector changes
|
||||
6. `validate_script` to verify the script still works
|
||||
|
||||
## Formatting Rules
|
||||
|
||||
### execute_editor_script
|
||||
The `code` parameter must be valid GDScript. Use `_mcp_print(value)` to return output.
|
||||
|
||||
```
|
||||
# Correct
|
||||
_mcp_print("hello")
|
||||
|
||||
# Correct - multi-line
|
||||
var nodes = []
|
||||
for child in EditorInterface.get_edited_scene_root().get_children():
|
||||
nodes.append(child.name)
|
||||
_mcp_print(str(nodes))
|
||||
```
|
||||
|
||||
### execute_game_script
|
||||
Same as above but runs inside the running game. Additional rules:
|
||||
- No nested functions (`func` inside `func` is invalid GDScript)
|
||||
- Use `.get("property")` instead of `.property` for safe access
|
||||
- Runs in a temporary node — use `get_tree()` to access the scene tree
|
||||
|
||||
### batch_add_nodes
|
||||
Pass an array of node definitions. Nodes are processed in order, so earlier nodes can be parents for later ones:
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{"type": "Node2D", "name": "Container", "parent_path": "."},
|
||||
{"type": "Sprite2D", "name": "Icon", "parent_path": "Container"},
|
||||
{"type": "Label", "name": "Title", "parent_path": "Container", "properties": {"text": "Hello"}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Prefer inspector properties over code** — When changing visual properties (colors, sizes, theme overrides, transforms, etc.), use `update_property` to set them directly on the node. This keeps values visible in the Godot inspector and easy to tweak. Only use GDScript when the property isn't available in the inspector or needs to be dynamic at runtime.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
1. **Never edit project.godot directly** — Use `set_project_setting` instead. The Godot editor overwrites the file.
|
||||
2. **GDScript type inference** — Use explicit type annotations in for-loops: `for item: String in array` instead of `for item in array`.
|
||||
3. **Reload after script changes** — After `create_script`, call `reload_project` if the script doesn't take effect.
|
||||
4. **Property values as strings** — Properties like position accept string format: `"Vector2(100, 200)"`, `"Color(1, 0, 0, 1)"`.
|
||||
5. **simulate_key duration** — Use short durations (0.3-0.5s) for precise movement. Integer seconds (1, 2, 3) cause overshooting.
|
||||
6. **compare_screenshots** — Pass file paths (`user://screenshot.png`), not base64 data.
|
||||
|
||||
## CLI Mode (Alternative to MCP Tools)
|
||||
|
||||
If MCP tools are unavailable or you have a terminal/bash tool, you can control Godot via the CLI.
|
||||
The CLI requires the server to be built first (`node build/setup.js install` in the server directory).
|
||||
|
||||
```bash
|
||||
# Discover available command groups
|
||||
node /path/to/server/build/cli.js --help
|
||||
|
||||
# Discover commands in a group
|
||||
node /path/to/server/build/cli.js scene --help
|
||||
|
||||
# Discover options for a specific command
|
||||
node /path/to/server/build/cli.js node add --help
|
||||
|
||||
# Execute commands
|
||||
node /path/to/server/build/cli.js project info
|
||||
node /path/to/server/build/cli.js scene tree
|
||||
node /path/to/server/build/cli.js node add --type CharacterBody3D --name Player --parent /root/Main
|
||||
node /path/to/server/build/cli.js script read --path res://player.gd
|
||||
node /path/to/server/build/cli.js scene play
|
||||
node /path/to/server/build/cli.js input key --key W --duration 0.5
|
||||
node /path/to/server/build/cli.js runtime tree
|
||||
```
|
||||
|
||||
**Command groups**: project, scene, node, script, editor, input, runtime
|
||||
|
||||
Always start by running `--help` to discover available commands. Use the CLI when MCP tools are not loaded or when you need to reduce context usage.
|
||||
@@ -0,0 +1,19 @@
|
||||
# Godot MCP Pro — AI Client Instructions
|
||||
|
||||
Copy the appropriate file to your project root so your AI assistant knows how to use Godot MCP Pro.
|
||||
|
||||
| Client | File to copy | Destination |
|
||||
|--------|-------------|-------------|
|
||||
| Claude Code | `CLAUDE.md` | Project root |
|
||||
| Codex CLI / OpenCode | `AGENTS.md` | Project root |
|
||||
| Gemini CLI | `GEMINI.md` | Project root |
|
||||
| Cursor | `godot-mcp-pro.mdc` | `.cursor/rules/godot-mcp-pro.mdc` |
|
||||
| Cline | `.clinerules` | Project root |
|
||||
| Windsurf | `.windsurfrules` | Project root |
|
||||
| Roo Code | `roo-godot-mcp-pro.md` | `.roo/rules/roo-godot-mcp-pro.md` |
|
||||
| JetBrains / Junie | `junie-guidelines.md` | `.junie/guidelines.md` |
|
||||
| Amazon Q | `amazonq-godot-mcp-pro.md` | `.amazonq/rules/amazonq-godot-mcp-pro.md` |
|
||||
| Continue | `continue-godot-mcp-pro.md` | `.continue/rules/godot-mcp-pro.md` |
|
||||
| Augment Code | `augment-godot-mcp-pro.md` | `.augment/instructions/godot-mcp-pro.md` |
|
||||
|
||||
All files contain the same instructions adapted for each client's format.
|
||||
@@ -0,0 +1,146 @@
|
||||
# Godot MCP Pro - AI Assistant Instructions
|
||||
|
||||
You have access to the Godot MCP Pro toolset for building and testing Godot games through the editor. Follow these rules carefully.
|
||||
|
||||
## Critical: Editor vs Runtime Tools
|
||||
|
||||
Tools are split into two categories. **Using a runtime tool without starting the game will always fail.**
|
||||
|
||||
### Editor Tools (always available)
|
||||
These work on the currently open scene in the Godot editor:
|
||||
- **Scene**: `get_scene_tree`, `create_scene`, `open_scene`, `save_scene`, `delete_scene`, `add_scene_instance`, `get_scene_file_content`, `get_scene_exports`
|
||||
- **Nodes**: `add_node`, `delete_node`, `duplicate_node`, `move_node`, `rename_node`, `update_property`, `get_node_properties`, `add_resource`, `set_anchor_preset`, `connect_signal`, `disconnect_signal`, `get_node_groups`, `set_node_groups`, `find_nodes_in_group`
|
||||
- **Scripts**: `create_script`, `read_script`, `edit_script`, `validate_script`, `attach_script`, `get_open_scripts`, `list_scripts`
|
||||
- **Project**: `get_project_info`, `get_project_settings`, `set_project_setting`, `get_project_statistics`, `get_filesystem_tree`, `get_input_actions`, `set_input_action`
|
||||
- **Editor**: `execute_editor_script`, `get_editor_errors`, `get_output_log`, `get_editor_screenshot`, `clear_output`, `reload_plugin`, `reload_project`
|
||||
- **Resources**: `create_resource`, `read_resource`, `edit_resource`, `get_resource_preview`
|
||||
- **Batch**: `batch_add_nodes`, `batch_set_property`, `find_nodes_by_type`, `find_signal_connections`, `find_node_references`, `get_scene_dependencies`, `cross_scene_set_property`
|
||||
- **3D**: `add_mesh_instance`, `setup_environment`, `setup_lighting`, `setup_camera_3d`, `setup_collision`, `setup_physics_body`, `set_material_3d`, `add_raycast`, `add_gridmap`
|
||||
- **Animation**: `create_animation`, `add_animation_track`, `set_animation_keyframe`, `list_animations`, `get_animation_info`, `remove_animation`
|
||||
- **Animation Tree**: `create_animation_tree`, `get_animation_tree_structure`, `add_state_machine_state`, `add_state_machine_transition`, `remove_state_machine_state`, `remove_state_machine_transition`, `set_blend_tree_node`, `set_tree_parameter`
|
||||
- **Audio**: `add_audio_player`, `add_audio_bus`, `add_audio_bus_effect`, `set_audio_bus`, `get_audio_bus_layout`, `get_audio_info`
|
||||
- **Navigation**: `setup_navigation_region`, `setup_navigation_agent`, `bake_navigation_mesh`, `set_navigation_layers`, `get_navigation_info`
|
||||
- **Particles**: `create_particles`, `set_particle_material`, `set_particle_color_gradient`, `apply_particle_preset`, `get_particle_info`
|
||||
- **Physics**: `get_physics_layers`, `set_physics_layers`, `get_collision_info`
|
||||
- **Shader**: `create_shader`, `read_shader`, `edit_shader`, `assign_shader_material`, `get_shader_params`, `set_shader_param`
|
||||
- **Theme**: `create_theme`, `get_theme_info`, `set_theme_color`, `set_theme_font_size`, `set_theme_constant`, `set_theme_stylebox`
|
||||
- **Tilemap**: `tilemap_get_info`, `tilemap_set_cell`, `tilemap_get_cell`, `tilemap_fill_rect`, `tilemap_clear`, `tilemap_get_used_cells`
|
||||
- **Export**: `list_export_presets`, `get_export_info`, `export_project`
|
||||
- **Analysis**: `analyze_scene_complexity`, `analyze_signal_flow`, `detect_circular_dependencies`, `find_unused_resources`, `get_performance_monitors`, `search_files`, `search_in_files`, `find_script_references`
|
||||
- **Profiling**: `get_editor_performance`
|
||||
|
||||
### Runtime Tools (require `play_scene` first)
|
||||
You MUST call `play_scene` before using any of these. They interact with the running game:
|
||||
- **Game State**: `get_game_scene_tree`, `get_game_node_properties`, `set_game_node_property`, `execute_game_script`, `get_game_screenshot`, `get_autoload`, `find_nodes_by_script`
|
||||
- **Input Simulation**: `simulate_key`, `simulate_mouse_click`, `simulate_mouse_move`, `simulate_action`, `simulate_sequence`
|
||||
- **Capture/Recording**: `capture_frames`, `record_frames`, `monitor_properties`, `start_recording`, `stop_recording`, `replay_recording`, `batch_get_properties`
|
||||
- **UI Interaction**: `find_ui_elements`, `click_button_by_text`, `wait_for_node`, `find_nearby_nodes`, `navigate_to`, `move_to`
|
||||
- **Testing**: `run_test_scenario`, `assert_node_state`, `assert_screen_text`, `run_stress_test`, `get_test_report`
|
||||
- **Screenshots**: `get_game_screenshot`, `compare_screenshots`
|
||||
- **Control**: `play_scene`, `stop_scene`
|
||||
|
||||
## Workflow Patterns
|
||||
|
||||
### Building a scene from scratch
|
||||
1. `create_scene` or `open_scene`
|
||||
2. Use `add_node` or `batch_add_nodes` to add nodes
|
||||
3. `create_script` + `attach_script` for behavior
|
||||
4. `save_scene`
|
||||
|
||||
### Testing gameplay
|
||||
1. Build scene with editor tools (above)
|
||||
2. `play_scene` to start the game
|
||||
3. Use `simulate_key`/`simulate_mouse_click` for input
|
||||
4. `get_game_screenshot` or `capture_frames` to observe results
|
||||
5. `stop_scene` when done
|
||||
|
||||
### Inspecting a project
|
||||
1. `get_project_info` for overview
|
||||
2. `get_scene_tree` for current scene structure
|
||||
3. `read_script` to read code
|
||||
4. `get_node_properties` for specific node details
|
||||
|
||||
### Migrating code properties to inspector
|
||||
When a script hardcodes visual properties (colors, sizes, positions, theme overrides) that should be in the inspector:
|
||||
1. `read_script` to find hardcoded property assignments (e.g. `modulate = Color(...)`, `add_theme_color_override(...)`)
|
||||
2. `get_node_properties` to see current inspector values
|
||||
3. `update_property` to set the same values as node properties in the inspector
|
||||
4. `edit_script` to remove the hardcoded lines from the script
|
||||
5. `save_scene` to persist the inspector changes
|
||||
6. `validate_script` to verify the script still works
|
||||
|
||||
## Formatting Rules
|
||||
|
||||
### execute_editor_script
|
||||
The `code` parameter must be valid GDScript. Use `_mcp_print(value)` to return output.
|
||||
|
||||
```
|
||||
# Correct
|
||||
_mcp_print("hello")
|
||||
|
||||
# Correct - multi-line
|
||||
var nodes = []
|
||||
for child in EditorInterface.get_edited_scene_root().get_children():
|
||||
nodes.append(child.name)
|
||||
_mcp_print(str(nodes))
|
||||
```
|
||||
|
||||
### execute_game_script
|
||||
Same as above but runs inside the running game. Additional rules:
|
||||
- No nested functions (`func` inside `func` is invalid GDScript)
|
||||
- Use `.get("property")` instead of `.property` for safe access
|
||||
- Runs in a temporary node — use `get_tree()` to access the scene tree
|
||||
|
||||
### batch_add_nodes
|
||||
Pass an array of node definitions. Nodes are processed in order, so earlier nodes can be parents for later ones:
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{"type": "Node2D", "name": "Container", "parent_path": "."},
|
||||
{"type": "Sprite2D", "name": "Icon", "parent_path": "Container"},
|
||||
{"type": "Label", "name": "Title", "parent_path": "Container", "properties": {"text": "Hello"}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Prefer inspector properties over code** — When changing visual properties (colors, sizes, theme overrides, transforms, etc.), use `update_property` to set them directly on the node. This keeps values visible in the Godot inspector and easy to tweak. Only use GDScript when the property isn't available in the inspector or needs to be dynamic at runtime.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
1. **Never edit project.godot directly** — Use `set_project_setting` instead. The Godot editor overwrites the file.
|
||||
2. **GDScript type inference** — Use explicit type annotations in for-loops: `for item: String in array` instead of `for item in array`.
|
||||
3. **Reload after script changes** — After `create_script`, call `reload_project` if the script doesn't take effect.
|
||||
4. **Property values as strings** — Properties like position accept string format: `"Vector2(100, 200)"`, `"Color(1, 0, 0, 1)"`.
|
||||
5. **simulate_key duration** — Use short durations (0.3-0.5s) for precise movement. Integer seconds (1, 2, 3) cause overshooting.
|
||||
6. **compare_screenshots** — Pass file paths (`user://screenshot.png`), not base64 data.
|
||||
|
||||
## CLI Mode (Alternative to MCP Tools)
|
||||
|
||||
If MCP tools are unavailable or you have a terminal/bash tool, you can control Godot via the CLI.
|
||||
The CLI requires the server to be built first (`node build/setup.js install` in the server directory).
|
||||
|
||||
```bash
|
||||
# Discover available command groups
|
||||
node /path/to/server/build/cli.js --help
|
||||
|
||||
# Discover commands in a group
|
||||
node /path/to/server/build/cli.js scene --help
|
||||
|
||||
# Discover options for a specific command
|
||||
node /path/to/server/build/cli.js node add --help
|
||||
|
||||
# Execute commands
|
||||
node /path/to/server/build/cli.js project info
|
||||
node /path/to/server/build/cli.js scene tree
|
||||
node /path/to/server/build/cli.js node add --type CharacterBody3D --name Player --parent /root/Main
|
||||
node /path/to/server/build/cli.js script read --path res://player.gd
|
||||
node /path/to/server/build/cli.js scene play
|
||||
node /path/to/server/build/cli.js input key --key W --duration 0.5
|
||||
node /path/to/server/build/cli.js runtime tree
|
||||
```
|
||||
|
||||
**Command groups**: project, scene, node, script, editor, input, runtime
|
||||
|
||||
Always start by running `--help` to discover available commands. Use the CLI when MCP tools are not loaded or when you need to reduce context usage.
|
||||
@@ -0,0 +1,146 @@
|
||||
# Godot MCP Pro - AI Assistant Instructions
|
||||
|
||||
You have access to the Godot MCP Pro toolset for building and testing Godot games through the editor. Follow these rules carefully.
|
||||
|
||||
## Critical: Editor vs Runtime Tools
|
||||
|
||||
Tools are split into two categories. **Using a runtime tool without starting the game will always fail.**
|
||||
|
||||
### Editor Tools (always available)
|
||||
These work on the currently open scene in the Godot editor:
|
||||
- **Scene**: `get_scene_tree`, `create_scene`, `open_scene`, `save_scene`, `delete_scene`, `add_scene_instance`, `get_scene_file_content`, `get_scene_exports`
|
||||
- **Nodes**: `add_node`, `delete_node`, `duplicate_node`, `move_node`, `rename_node`, `update_property`, `get_node_properties`, `add_resource`, `set_anchor_preset`, `connect_signal`, `disconnect_signal`, `get_node_groups`, `set_node_groups`, `find_nodes_in_group`
|
||||
- **Scripts**: `create_script`, `read_script`, `edit_script`, `validate_script`, `attach_script`, `get_open_scripts`, `list_scripts`
|
||||
- **Project**: `get_project_info`, `get_project_settings`, `set_project_setting`, `get_project_statistics`, `get_filesystem_tree`, `get_input_actions`, `set_input_action`
|
||||
- **Editor**: `execute_editor_script`, `get_editor_errors`, `get_output_log`, `get_editor_screenshot`, `clear_output`, `reload_plugin`, `reload_project`
|
||||
- **Resources**: `create_resource`, `read_resource`, `edit_resource`, `get_resource_preview`
|
||||
- **Batch**: `batch_add_nodes`, `batch_set_property`, `find_nodes_by_type`, `find_signal_connections`, `find_node_references`, `get_scene_dependencies`, `cross_scene_set_property`
|
||||
- **3D**: `add_mesh_instance`, `setup_environment`, `setup_lighting`, `setup_camera_3d`, `setup_collision`, `setup_physics_body`, `set_material_3d`, `add_raycast`, `add_gridmap`
|
||||
- **Animation**: `create_animation`, `add_animation_track`, `set_animation_keyframe`, `list_animations`, `get_animation_info`, `remove_animation`
|
||||
- **Animation Tree**: `create_animation_tree`, `get_animation_tree_structure`, `add_state_machine_state`, `add_state_machine_transition`, `remove_state_machine_state`, `remove_state_machine_transition`, `set_blend_tree_node`, `set_tree_parameter`
|
||||
- **Audio**: `add_audio_player`, `add_audio_bus`, `add_audio_bus_effect`, `set_audio_bus`, `get_audio_bus_layout`, `get_audio_info`
|
||||
- **Navigation**: `setup_navigation_region`, `setup_navigation_agent`, `bake_navigation_mesh`, `set_navigation_layers`, `get_navigation_info`
|
||||
- **Particles**: `create_particles`, `set_particle_material`, `set_particle_color_gradient`, `apply_particle_preset`, `get_particle_info`
|
||||
- **Physics**: `get_physics_layers`, `set_physics_layers`, `get_collision_info`
|
||||
- **Shader**: `create_shader`, `read_shader`, `edit_shader`, `assign_shader_material`, `get_shader_params`, `set_shader_param`
|
||||
- **Theme**: `create_theme`, `get_theme_info`, `set_theme_color`, `set_theme_font_size`, `set_theme_constant`, `set_theme_stylebox`
|
||||
- **Tilemap**: `tilemap_get_info`, `tilemap_set_cell`, `tilemap_get_cell`, `tilemap_fill_rect`, `tilemap_clear`, `tilemap_get_used_cells`
|
||||
- **Export**: `list_export_presets`, `get_export_info`, `export_project`
|
||||
- **Analysis**: `analyze_scene_complexity`, `analyze_signal_flow`, `detect_circular_dependencies`, `find_unused_resources`, `get_performance_monitors`, `search_files`, `search_in_files`, `find_script_references`
|
||||
- **Profiling**: `get_editor_performance`
|
||||
|
||||
### Runtime Tools (require `play_scene` first)
|
||||
You MUST call `play_scene` before using any of these. They interact with the running game:
|
||||
- **Game State**: `get_game_scene_tree`, `get_game_node_properties`, `set_game_node_property`, `execute_game_script`, `get_game_screenshot`, `get_autoload`, `find_nodes_by_script`
|
||||
- **Input Simulation**: `simulate_key`, `simulate_mouse_click`, `simulate_mouse_move`, `simulate_action`, `simulate_sequence`
|
||||
- **Capture/Recording**: `capture_frames`, `record_frames`, `monitor_properties`, `start_recording`, `stop_recording`, `replay_recording`, `batch_get_properties`
|
||||
- **UI Interaction**: `find_ui_elements`, `click_button_by_text`, `wait_for_node`, `find_nearby_nodes`, `navigate_to`, `move_to`
|
||||
- **Testing**: `run_test_scenario`, `assert_node_state`, `assert_screen_text`, `run_stress_test`, `get_test_report`
|
||||
- **Screenshots**: `get_game_screenshot`, `compare_screenshots`
|
||||
- **Control**: `play_scene`, `stop_scene`
|
||||
|
||||
## Workflow Patterns
|
||||
|
||||
### Building a scene from scratch
|
||||
1. `create_scene` or `open_scene`
|
||||
2. Use `add_node` or `batch_add_nodes` to add nodes
|
||||
3. `create_script` + `attach_script` for behavior
|
||||
4. `save_scene`
|
||||
|
||||
### Testing gameplay
|
||||
1. Build scene with editor tools (above)
|
||||
2. `play_scene` to start the game
|
||||
3. Use `simulate_key`/`simulate_mouse_click` for input
|
||||
4. `get_game_screenshot` or `capture_frames` to observe results
|
||||
5. `stop_scene` when done
|
||||
|
||||
### Inspecting a project
|
||||
1. `get_project_info` for overview
|
||||
2. `get_scene_tree` for current scene structure
|
||||
3. `read_script` to read code
|
||||
4. `get_node_properties` for specific node details
|
||||
|
||||
### Migrating code properties to inspector
|
||||
When a script hardcodes visual properties (colors, sizes, positions, theme overrides) that should be in the inspector:
|
||||
1. `read_script` to find hardcoded property assignments (e.g. `modulate = Color(...)`, `add_theme_color_override(...)`)
|
||||
2. `get_node_properties` to see current inspector values
|
||||
3. `update_property` to set the same values as node properties in the inspector
|
||||
4. `edit_script` to remove the hardcoded lines from the script
|
||||
5. `save_scene` to persist the inspector changes
|
||||
6. `validate_script` to verify the script still works
|
||||
|
||||
## Formatting Rules
|
||||
|
||||
### execute_editor_script
|
||||
The `code` parameter must be valid GDScript. Use `_mcp_print(value)` to return output.
|
||||
|
||||
```
|
||||
# Correct
|
||||
_mcp_print("hello")
|
||||
|
||||
# Correct - multi-line
|
||||
var nodes = []
|
||||
for child in EditorInterface.get_edited_scene_root().get_children():
|
||||
nodes.append(child.name)
|
||||
_mcp_print(str(nodes))
|
||||
```
|
||||
|
||||
### execute_game_script
|
||||
Same as above but runs inside the running game. Additional rules:
|
||||
- No nested functions (`func` inside `func` is invalid GDScript)
|
||||
- Use `.get("property")` instead of `.property` for safe access
|
||||
- Runs in a temporary node — use `get_tree()` to access the scene tree
|
||||
|
||||
### batch_add_nodes
|
||||
Pass an array of node definitions. Nodes are processed in order, so earlier nodes can be parents for later ones:
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{"type": "Node2D", "name": "Container", "parent_path": "."},
|
||||
{"type": "Sprite2D", "name": "Icon", "parent_path": "Container"},
|
||||
{"type": "Label", "name": "Title", "parent_path": "Container", "properties": {"text": "Hello"}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Prefer inspector properties over code** — When changing visual properties (colors, sizes, theme overrides, transforms, etc.), use `update_property` to set them directly on the node. This keeps values visible in the Godot inspector and easy to tweak. Only use GDScript when the property isn't available in the inspector or needs to be dynamic at runtime.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
1. **Never edit project.godot directly** — Use `set_project_setting` instead. The Godot editor overwrites the file.
|
||||
2. **GDScript type inference** — Use explicit type annotations in for-loops: `for item: String in array` instead of `for item in array`.
|
||||
3. **Reload after script changes** — After `create_script`, call `reload_project` if the script doesn't take effect.
|
||||
4. **Property values as strings** — Properties like position accept string format: `"Vector2(100, 200)"`, `"Color(1, 0, 0, 1)"`.
|
||||
5. **simulate_key duration** — Use short durations (0.3-0.5s) for precise movement. Integer seconds (1, 2, 3) cause overshooting.
|
||||
6. **compare_screenshots** — Pass file paths (`user://screenshot.png`), not base64 data.
|
||||
|
||||
## CLI Mode (Alternative to MCP Tools)
|
||||
|
||||
If MCP tools are unavailable or you have a terminal/bash tool, you can control Godot via the CLI.
|
||||
The CLI requires the server to be built first (`node build/setup.js install` in the server directory).
|
||||
|
||||
```bash
|
||||
# Discover available command groups
|
||||
node /path/to/server/build/cli.js --help
|
||||
|
||||
# Discover commands in a group
|
||||
node /path/to/server/build/cli.js scene --help
|
||||
|
||||
# Discover options for a specific command
|
||||
node /path/to/server/build/cli.js node add --help
|
||||
|
||||
# Execute commands
|
||||
node /path/to/server/build/cli.js project info
|
||||
node /path/to/server/build/cli.js scene tree
|
||||
node /path/to/server/build/cli.js node add --type CharacterBody3D --name Player --parent /root/Main
|
||||
node /path/to/server/build/cli.js script read --path res://player.gd
|
||||
node /path/to/server/build/cli.js scene play
|
||||
node /path/to/server/build/cli.js input key --key W --duration 0.5
|
||||
node /path/to/server/build/cli.js runtime tree
|
||||
```
|
||||
|
||||
**Command groups**: project, scene, node, script, editor, input, runtime
|
||||
|
||||
Always start by running `--help` to discover available commands. Use the CLI when MCP tools are not loaded or when you need to reduce context usage.
|
||||
@@ -0,0 +1,146 @@
|
||||
# Godot MCP Pro - AI Assistant Instructions
|
||||
|
||||
You have access to the Godot MCP Pro toolset for building and testing Godot games through the editor. Follow these rules carefully.
|
||||
|
||||
## Critical: Editor vs Runtime Tools
|
||||
|
||||
Tools are split into two categories. **Using a runtime tool without starting the game will always fail.**
|
||||
|
||||
### Editor Tools (always available)
|
||||
These work on the currently open scene in the Godot editor:
|
||||
- **Scene**: `get_scene_tree`, `create_scene`, `open_scene`, `save_scene`, `delete_scene`, `add_scene_instance`, `get_scene_file_content`, `get_scene_exports`
|
||||
- **Nodes**: `add_node`, `delete_node`, `duplicate_node`, `move_node`, `rename_node`, `update_property`, `get_node_properties`, `add_resource`, `set_anchor_preset`, `connect_signal`, `disconnect_signal`, `get_node_groups`, `set_node_groups`, `find_nodes_in_group`
|
||||
- **Scripts**: `create_script`, `read_script`, `edit_script`, `validate_script`, `attach_script`, `get_open_scripts`, `list_scripts`
|
||||
- **Project**: `get_project_info`, `get_project_settings`, `set_project_setting`, `get_project_statistics`, `get_filesystem_tree`, `get_input_actions`, `set_input_action`
|
||||
- **Editor**: `execute_editor_script`, `get_editor_errors`, `get_output_log`, `get_editor_screenshot`, `clear_output`, `reload_plugin`, `reload_project`
|
||||
- **Resources**: `create_resource`, `read_resource`, `edit_resource`, `get_resource_preview`
|
||||
- **Batch**: `batch_add_nodes`, `batch_set_property`, `find_nodes_by_type`, `find_signal_connections`, `find_node_references`, `get_scene_dependencies`, `cross_scene_set_property`
|
||||
- **3D**: `add_mesh_instance`, `setup_environment`, `setup_lighting`, `setup_camera_3d`, `setup_collision`, `setup_physics_body`, `set_material_3d`, `add_raycast`, `add_gridmap`
|
||||
- **Animation**: `create_animation`, `add_animation_track`, `set_animation_keyframe`, `list_animations`, `get_animation_info`, `remove_animation`
|
||||
- **Animation Tree**: `create_animation_tree`, `get_animation_tree_structure`, `add_state_machine_state`, `add_state_machine_transition`, `remove_state_machine_state`, `remove_state_machine_transition`, `set_blend_tree_node`, `set_tree_parameter`
|
||||
- **Audio**: `add_audio_player`, `add_audio_bus`, `add_audio_bus_effect`, `set_audio_bus`, `get_audio_bus_layout`, `get_audio_info`
|
||||
- **Navigation**: `setup_navigation_region`, `setup_navigation_agent`, `bake_navigation_mesh`, `set_navigation_layers`, `get_navigation_info`
|
||||
- **Particles**: `create_particles`, `set_particle_material`, `set_particle_color_gradient`, `apply_particle_preset`, `get_particle_info`
|
||||
- **Physics**: `get_physics_layers`, `set_physics_layers`, `get_collision_info`
|
||||
- **Shader**: `create_shader`, `read_shader`, `edit_shader`, `assign_shader_material`, `get_shader_params`, `set_shader_param`
|
||||
- **Theme**: `create_theme`, `get_theme_info`, `set_theme_color`, `set_theme_font_size`, `set_theme_constant`, `set_theme_stylebox`
|
||||
- **Tilemap**: `tilemap_get_info`, `tilemap_set_cell`, `tilemap_get_cell`, `tilemap_fill_rect`, `tilemap_clear`, `tilemap_get_used_cells`
|
||||
- **Export**: `list_export_presets`, `get_export_info`, `export_project`
|
||||
- **Analysis**: `analyze_scene_complexity`, `analyze_signal_flow`, `detect_circular_dependencies`, `find_unused_resources`, `get_performance_monitors`, `search_files`, `search_in_files`, `find_script_references`
|
||||
- **Profiling**: `get_editor_performance`
|
||||
|
||||
### Runtime Tools (require `play_scene` first)
|
||||
You MUST call `play_scene` before using any of these. They interact with the running game:
|
||||
- **Game State**: `get_game_scene_tree`, `get_game_node_properties`, `set_game_node_property`, `execute_game_script`, `get_game_screenshot`, `get_autoload`, `find_nodes_by_script`
|
||||
- **Input Simulation**: `simulate_key`, `simulate_mouse_click`, `simulate_mouse_move`, `simulate_action`, `simulate_sequence`
|
||||
- **Capture/Recording**: `capture_frames`, `record_frames`, `monitor_properties`, `start_recording`, `stop_recording`, `replay_recording`, `batch_get_properties`
|
||||
- **UI Interaction**: `find_ui_elements`, `click_button_by_text`, `wait_for_node`, `find_nearby_nodes`, `navigate_to`, `move_to`
|
||||
- **Testing**: `run_test_scenario`, `assert_node_state`, `assert_screen_text`, `run_stress_test`, `get_test_report`
|
||||
- **Screenshots**: `get_game_screenshot`, `compare_screenshots`
|
||||
- **Control**: `play_scene`, `stop_scene`
|
||||
|
||||
## Workflow Patterns
|
||||
|
||||
### Building a scene from scratch
|
||||
1. `create_scene` or `open_scene`
|
||||
2. Use `add_node` or `batch_add_nodes` to add nodes
|
||||
3. `create_script` + `attach_script` for behavior
|
||||
4. `save_scene`
|
||||
|
||||
### Testing gameplay
|
||||
1. Build scene with editor tools (above)
|
||||
2. `play_scene` to start the game
|
||||
3. Use `simulate_key`/`simulate_mouse_click` for input
|
||||
4. `get_game_screenshot` or `capture_frames` to observe results
|
||||
5. `stop_scene` when done
|
||||
|
||||
### Inspecting a project
|
||||
1. `get_project_info` for overview
|
||||
2. `get_scene_tree` for current scene structure
|
||||
3. `read_script` to read code
|
||||
4. `get_node_properties` for specific node details
|
||||
|
||||
### Migrating code properties to inspector
|
||||
When a script hardcodes visual properties (colors, sizes, positions, theme overrides) that should be in the inspector:
|
||||
1. `read_script` to find hardcoded property assignments (e.g. `modulate = Color(...)`, `add_theme_color_override(...)`)
|
||||
2. `get_node_properties` to see current inspector values
|
||||
3. `update_property` to set the same values as node properties in the inspector
|
||||
4. `edit_script` to remove the hardcoded lines from the script
|
||||
5. `save_scene` to persist the inspector changes
|
||||
6. `validate_script` to verify the script still works
|
||||
|
||||
## Formatting Rules
|
||||
|
||||
### execute_editor_script
|
||||
The `code` parameter must be valid GDScript. Use `_mcp_print(value)` to return output.
|
||||
|
||||
```
|
||||
# Correct
|
||||
_mcp_print("hello")
|
||||
|
||||
# Correct - multi-line
|
||||
var nodes = []
|
||||
for child in EditorInterface.get_edited_scene_root().get_children():
|
||||
nodes.append(child.name)
|
||||
_mcp_print(str(nodes))
|
||||
```
|
||||
|
||||
### execute_game_script
|
||||
Same as above but runs inside the running game. Additional rules:
|
||||
- No nested functions (`func` inside `func` is invalid GDScript)
|
||||
- Use `.get("property")` instead of `.property` for safe access
|
||||
- Runs in a temporary node — use `get_tree()` to access the scene tree
|
||||
|
||||
### batch_add_nodes
|
||||
Pass an array of node definitions. Nodes are processed in order, so earlier nodes can be parents for later ones:
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{"type": "Node2D", "name": "Container", "parent_path": "."},
|
||||
{"type": "Sprite2D", "name": "Icon", "parent_path": "Container"},
|
||||
{"type": "Label", "name": "Title", "parent_path": "Container", "properties": {"text": "Hello"}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Prefer inspector properties over code** — When changing visual properties (colors, sizes, theme overrides, transforms, etc.), use `update_property` to set them directly on the node. This keeps values visible in the Godot inspector and easy to tweak. Only use GDScript when the property isn't available in the inspector or needs to be dynamic at runtime.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
1. **Never edit project.godot directly** — Use `set_project_setting` instead. The Godot editor overwrites the file.
|
||||
2. **GDScript type inference** — Use explicit type annotations in for-loops: `for item: String in array` instead of `for item in array`.
|
||||
3. **Reload after script changes** — After `create_script`, call `reload_project` if the script doesn't take effect.
|
||||
4. **Property values as strings** — Properties like position accept string format: `"Vector2(100, 200)"`, `"Color(1, 0, 0, 1)"`.
|
||||
5. **simulate_key duration** — Use short durations (0.3-0.5s) for precise movement. Integer seconds (1, 2, 3) cause overshooting.
|
||||
6. **compare_screenshots** — Pass file paths (`user://screenshot.png`), not base64 data.
|
||||
|
||||
## CLI Mode (Alternative to MCP Tools)
|
||||
|
||||
If MCP tools are unavailable or you have a terminal/bash tool, you can control Godot via the CLI.
|
||||
The CLI requires the server to be built first (`node build/setup.js install` in the server directory).
|
||||
|
||||
```bash
|
||||
# Discover available command groups
|
||||
node /path/to/server/build/cli.js --help
|
||||
|
||||
# Discover commands in a group
|
||||
node /path/to/server/build/cli.js scene --help
|
||||
|
||||
# Discover options for a specific command
|
||||
node /path/to/server/build/cli.js node add --help
|
||||
|
||||
# Execute commands
|
||||
node /path/to/server/build/cli.js project info
|
||||
node /path/to/server/build/cli.js scene tree
|
||||
node /path/to/server/build/cli.js node add --type CharacterBody3D --name Player --parent /root/Main
|
||||
node /path/to/server/build/cli.js script read --path res://player.gd
|
||||
node /path/to/server/build/cli.js scene play
|
||||
node /path/to/server/build/cli.js input key --key W --duration 0.5
|
||||
node /path/to/server/build/cli.js runtime tree
|
||||
```
|
||||
|
||||
**Command groups**: project, scene, node, script, editor, input, runtime
|
||||
|
||||
Always start by running `--help` to discover available commands. Use the CLI when MCP tools are not loaded or when you need to reduce context usage.
|
||||
@@ -0,0 +1,151 @@
|
||||
---
|
||||
description: Godot MCP Pro instructions for controlling Godot editor via MCP tools and CLI
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Godot MCP Pro - AI Assistant Instructions
|
||||
|
||||
You have access to the Godot MCP Pro toolset for building and testing Godot games through the editor. Follow these rules carefully.
|
||||
|
||||
## Critical: Editor vs Runtime Tools
|
||||
|
||||
Tools are split into two categories. **Using a runtime tool without starting the game will always fail.**
|
||||
|
||||
### Editor Tools (always available)
|
||||
These work on the currently open scene in the Godot editor:
|
||||
- **Scene**: `get_scene_tree`, `create_scene`, `open_scene`, `save_scene`, `delete_scene`, `add_scene_instance`, `get_scene_file_content`, `get_scene_exports`
|
||||
- **Nodes**: `add_node`, `delete_node`, `duplicate_node`, `move_node`, `rename_node`, `update_property`, `get_node_properties`, `add_resource`, `set_anchor_preset`, `connect_signal`, `disconnect_signal`, `get_node_groups`, `set_node_groups`, `find_nodes_in_group`
|
||||
- **Scripts**: `create_script`, `read_script`, `edit_script`, `validate_script`, `attach_script`, `get_open_scripts`, `list_scripts`
|
||||
- **Project**: `get_project_info`, `get_project_settings`, `set_project_setting`, `get_project_statistics`, `get_filesystem_tree`, `get_input_actions`, `set_input_action`
|
||||
- **Editor**: `execute_editor_script`, `get_editor_errors`, `get_output_log`, `get_editor_screenshot`, `clear_output`, `reload_plugin`, `reload_project`
|
||||
- **Resources**: `create_resource`, `read_resource`, `edit_resource`, `get_resource_preview`
|
||||
- **Batch**: `batch_add_nodes`, `batch_set_property`, `find_nodes_by_type`, `find_signal_connections`, `find_node_references`, `get_scene_dependencies`, `cross_scene_set_property`
|
||||
- **3D**: `add_mesh_instance`, `setup_environment`, `setup_lighting`, `setup_camera_3d`, `setup_collision`, `setup_physics_body`, `set_material_3d`, `add_raycast`, `add_gridmap`
|
||||
- **Animation**: `create_animation`, `add_animation_track`, `set_animation_keyframe`, `list_animations`, `get_animation_info`, `remove_animation`
|
||||
- **Animation Tree**: `create_animation_tree`, `get_animation_tree_structure`, `add_state_machine_state`, `add_state_machine_transition`, `remove_state_machine_state`, `remove_state_machine_transition`, `set_blend_tree_node`, `set_tree_parameter`
|
||||
- **Audio**: `add_audio_player`, `add_audio_bus`, `add_audio_bus_effect`, `set_audio_bus`, `get_audio_bus_layout`, `get_audio_info`
|
||||
- **Navigation**: `setup_navigation_region`, `setup_navigation_agent`, `bake_navigation_mesh`, `set_navigation_layers`, `get_navigation_info`
|
||||
- **Particles**: `create_particles`, `set_particle_material`, `set_particle_color_gradient`, `apply_particle_preset`, `get_particle_info`
|
||||
- **Physics**: `get_physics_layers`, `set_physics_layers`, `get_collision_info`
|
||||
- **Shader**: `create_shader`, `read_shader`, `edit_shader`, `assign_shader_material`, `get_shader_params`, `set_shader_param`
|
||||
- **Theme**: `create_theme`, `get_theme_info`, `set_theme_color`, `set_theme_font_size`, `set_theme_constant`, `set_theme_stylebox`
|
||||
- **Tilemap**: `tilemap_get_info`, `tilemap_set_cell`, `tilemap_get_cell`, `tilemap_fill_rect`, `tilemap_clear`, `tilemap_get_used_cells`
|
||||
- **Export**: `list_export_presets`, `get_export_info`, `export_project`
|
||||
- **Analysis**: `analyze_scene_complexity`, `analyze_signal_flow`, `detect_circular_dependencies`, `find_unused_resources`, `get_performance_monitors`, `search_files`, `search_in_files`, `find_script_references`
|
||||
- **Profiling**: `get_editor_performance`
|
||||
|
||||
### Runtime Tools (require `play_scene` first)
|
||||
You MUST call `play_scene` before using any of these. They interact with the running game:
|
||||
- **Game State**: `get_game_scene_tree`, `get_game_node_properties`, `set_game_node_property`, `execute_game_script`, `get_game_screenshot`, `get_autoload`, `find_nodes_by_script`
|
||||
- **Input Simulation**: `simulate_key`, `simulate_mouse_click`, `simulate_mouse_move`, `simulate_action`, `simulate_sequence`
|
||||
- **Capture/Recording**: `capture_frames`, `record_frames`, `monitor_properties`, `start_recording`, `stop_recording`, `replay_recording`, `batch_get_properties`
|
||||
- **UI Interaction**: `find_ui_elements`, `click_button_by_text`, `wait_for_node`, `find_nearby_nodes`, `navigate_to`, `move_to`
|
||||
- **Testing**: `run_test_scenario`, `assert_node_state`, `assert_screen_text`, `run_stress_test`, `get_test_report`
|
||||
- **Screenshots**: `get_game_screenshot`, `compare_screenshots`
|
||||
- **Control**: `play_scene`, `stop_scene`
|
||||
|
||||
## Workflow Patterns
|
||||
|
||||
### Building a scene from scratch
|
||||
1. `create_scene` or `open_scene`
|
||||
2. Use `add_node` or `batch_add_nodes` to add nodes
|
||||
3. `create_script` + `attach_script` for behavior
|
||||
4. `save_scene`
|
||||
|
||||
### Testing gameplay
|
||||
1. Build scene with editor tools (above)
|
||||
2. `play_scene` to start the game
|
||||
3. Use `simulate_key`/`simulate_mouse_click` for input
|
||||
4. `get_game_screenshot` or `capture_frames` to observe results
|
||||
5. `stop_scene` when done
|
||||
|
||||
### Inspecting a project
|
||||
1. `get_project_info` for overview
|
||||
2. `get_scene_tree` for current scene structure
|
||||
3. `read_script` to read code
|
||||
4. `get_node_properties` for specific node details
|
||||
|
||||
### Migrating code properties to inspector
|
||||
When a script hardcodes visual properties (colors, sizes, positions, theme overrides) that should be in the inspector:
|
||||
1. `read_script` to find hardcoded property assignments (e.g. `modulate = Color(...)`, `add_theme_color_override(...)`)
|
||||
2. `get_node_properties` to see current inspector values
|
||||
3. `update_property` to set the same values as node properties in the inspector
|
||||
4. `edit_script` to remove the hardcoded lines from the script
|
||||
5. `save_scene` to persist the inspector changes
|
||||
6. `validate_script` to verify the script still works
|
||||
|
||||
## Formatting Rules
|
||||
|
||||
### execute_editor_script
|
||||
The `code` parameter must be valid GDScript. Use `_mcp_print(value)` to return output.
|
||||
|
||||
```
|
||||
# Correct
|
||||
_mcp_print("hello")
|
||||
|
||||
# Correct - multi-line
|
||||
var nodes = []
|
||||
for child in EditorInterface.get_edited_scene_root().get_children():
|
||||
nodes.append(child.name)
|
||||
_mcp_print(str(nodes))
|
||||
```
|
||||
|
||||
### execute_game_script
|
||||
Same as above but runs inside the running game. Additional rules:
|
||||
- No nested functions (`func` inside `func` is invalid GDScript)
|
||||
- Use `.get("property")` instead of `.property` for safe access
|
||||
- Runs in a temporary node — use `get_tree()` to access the scene tree
|
||||
|
||||
### batch_add_nodes
|
||||
Pass an array of node definitions. Nodes are processed in order, so earlier nodes can be parents for later ones:
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{"type": "Node2D", "name": "Container", "parent_path": "."},
|
||||
{"type": "Sprite2D", "name": "Icon", "parent_path": "Container"},
|
||||
{"type": "Label", "name": "Title", "parent_path": "Container", "properties": {"text": "Hello"}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Prefer inspector properties over code** — When changing visual properties (colors, sizes, theme overrides, transforms, etc.), use `update_property` to set them directly on the node. This keeps values visible in the Godot inspector and easy to tweak. Only use GDScript when the property isn't available in the inspector or needs to be dynamic at runtime.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
1. **Never edit project.godot directly** — Use `set_project_setting` instead. The Godot editor overwrites the file.
|
||||
2. **GDScript type inference** — Use explicit type annotations in for-loops: `for item: String in array` instead of `for item in array`.
|
||||
3. **Reload after script changes** — After `create_script`, call `reload_project` if the script doesn't take effect.
|
||||
4. **Property values as strings** — Properties like position accept string format: `"Vector2(100, 200)"`, `"Color(1, 0, 0, 1)"`.
|
||||
5. **simulate_key duration** — Use short durations (0.3-0.5s) for precise movement. Integer seconds (1, 2, 3) cause overshooting.
|
||||
6. **compare_screenshots** — Pass file paths (`user://screenshot.png`), not base64 data.
|
||||
|
||||
## CLI Mode (Alternative to MCP Tools)
|
||||
|
||||
If MCP tools are unavailable or you have a terminal/bash tool, you can control Godot via the CLI.
|
||||
The CLI requires the server to be built first (`node build/setup.js install` in the server directory).
|
||||
|
||||
```bash
|
||||
# Discover available command groups
|
||||
node /path/to/server/build/cli.js --help
|
||||
|
||||
# Discover commands in a group
|
||||
node /path/to/server/build/cli.js scene --help
|
||||
|
||||
# Discover options for a specific command
|
||||
node /path/to/server/build/cli.js node add --help
|
||||
|
||||
# Execute commands
|
||||
node /path/to/server/build/cli.js project info
|
||||
node /path/to/server/build/cli.js scene tree
|
||||
node /path/to/server/build/cli.js node add --type CharacterBody3D --name Player --parent /root/Main
|
||||
node /path/to/server/build/cli.js script read --path res://player.gd
|
||||
node /path/to/server/build/cli.js scene play
|
||||
node /path/to/server/build/cli.js input key --key W --duration 0.5
|
||||
node /path/to/server/build/cli.js runtime tree
|
||||
```
|
||||
|
||||
**Command groups**: project, scene, node, script, editor, input, runtime
|
||||
|
||||
Always start by running `--help` to discover available commands. Use the CLI when MCP tools are not loaded or when you need to reduce context usage.
|
||||
@@ -0,0 +1,146 @@
|
||||
# Godot MCP Pro - AI Assistant Instructions
|
||||
|
||||
You have access to the Godot MCP Pro toolset for building and testing Godot games through the editor. Follow these rules carefully.
|
||||
|
||||
## Critical: Editor vs Runtime Tools
|
||||
|
||||
Tools are split into two categories. **Using a runtime tool without starting the game will always fail.**
|
||||
|
||||
### Editor Tools (always available)
|
||||
These work on the currently open scene in the Godot editor:
|
||||
- **Scene**: `get_scene_tree`, `create_scene`, `open_scene`, `save_scene`, `delete_scene`, `add_scene_instance`, `get_scene_file_content`, `get_scene_exports`
|
||||
- **Nodes**: `add_node`, `delete_node`, `duplicate_node`, `move_node`, `rename_node`, `update_property`, `get_node_properties`, `add_resource`, `set_anchor_preset`, `connect_signal`, `disconnect_signal`, `get_node_groups`, `set_node_groups`, `find_nodes_in_group`
|
||||
- **Scripts**: `create_script`, `read_script`, `edit_script`, `validate_script`, `attach_script`, `get_open_scripts`, `list_scripts`
|
||||
- **Project**: `get_project_info`, `get_project_settings`, `set_project_setting`, `get_project_statistics`, `get_filesystem_tree`, `get_input_actions`, `set_input_action`
|
||||
- **Editor**: `execute_editor_script`, `get_editor_errors`, `get_output_log`, `get_editor_screenshot`, `clear_output`, `reload_plugin`, `reload_project`
|
||||
- **Resources**: `create_resource`, `read_resource`, `edit_resource`, `get_resource_preview`
|
||||
- **Batch**: `batch_add_nodes`, `batch_set_property`, `find_nodes_by_type`, `find_signal_connections`, `find_node_references`, `get_scene_dependencies`, `cross_scene_set_property`
|
||||
- **3D**: `add_mesh_instance`, `setup_environment`, `setup_lighting`, `setup_camera_3d`, `setup_collision`, `setup_physics_body`, `set_material_3d`, `add_raycast`, `add_gridmap`
|
||||
- **Animation**: `create_animation`, `add_animation_track`, `set_animation_keyframe`, `list_animations`, `get_animation_info`, `remove_animation`
|
||||
- **Animation Tree**: `create_animation_tree`, `get_animation_tree_structure`, `add_state_machine_state`, `add_state_machine_transition`, `remove_state_machine_state`, `remove_state_machine_transition`, `set_blend_tree_node`, `set_tree_parameter`
|
||||
- **Audio**: `add_audio_player`, `add_audio_bus`, `add_audio_bus_effect`, `set_audio_bus`, `get_audio_bus_layout`, `get_audio_info`
|
||||
- **Navigation**: `setup_navigation_region`, `setup_navigation_agent`, `bake_navigation_mesh`, `set_navigation_layers`, `get_navigation_info`
|
||||
- **Particles**: `create_particles`, `set_particle_material`, `set_particle_color_gradient`, `apply_particle_preset`, `get_particle_info`
|
||||
- **Physics**: `get_physics_layers`, `set_physics_layers`, `get_collision_info`
|
||||
- **Shader**: `create_shader`, `read_shader`, `edit_shader`, `assign_shader_material`, `get_shader_params`, `set_shader_param`
|
||||
- **Theme**: `create_theme`, `get_theme_info`, `set_theme_color`, `set_theme_font_size`, `set_theme_constant`, `set_theme_stylebox`
|
||||
- **Tilemap**: `tilemap_get_info`, `tilemap_set_cell`, `tilemap_get_cell`, `tilemap_fill_rect`, `tilemap_clear`, `tilemap_get_used_cells`
|
||||
- **Export**: `list_export_presets`, `get_export_info`, `export_project`
|
||||
- **Analysis**: `analyze_scene_complexity`, `analyze_signal_flow`, `detect_circular_dependencies`, `find_unused_resources`, `get_performance_monitors`, `search_files`, `search_in_files`, `find_script_references`
|
||||
- **Profiling**: `get_editor_performance`
|
||||
|
||||
### Runtime Tools (require `play_scene` first)
|
||||
You MUST call `play_scene` before using any of these. They interact with the running game:
|
||||
- **Game State**: `get_game_scene_tree`, `get_game_node_properties`, `set_game_node_property`, `execute_game_script`, `get_game_screenshot`, `get_autoload`, `find_nodes_by_script`
|
||||
- **Input Simulation**: `simulate_key`, `simulate_mouse_click`, `simulate_mouse_move`, `simulate_action`, `simulate_sequence`
|
||||
- **Capture/Recording**: `capture_frames`, `record_frames`, `monitor_properties`, `start_recording`, `stop_recording`, `replay_recording`, `batch_get_properties`
|
||||
- **UI Interaction**: `find_ui_elements`, `click_button_by_text`, `wait_for_node`, `find_nearby_nodes`, `navigate_to`, `move_to`
|
||||
- **Testing**: `run_test_scenario`, `assert_node_state`, `assert_screen_text`, `run_stress_test`, `get_test_report`
|
||||
- **Screenshots**: `get_game_screenshot`, `compare_screenshots`
|
||||
- **Control**: `play_scene`, `stop_scene`
|
||||
|
||||
## Workflow Patterns
|
||||
|
||||
### Building a scene from scratch
|
||||
1. `create_scene` or `open_scene`
|
||||
2. Use `add_node` or `batch_add_nodes` to add nodes
|
||||
3. `create_script` + `attach_script` for behavior
|
||||
4. `save_scene`
|
||||
|
||||
### Testing gameplay
|
||||
1. Build scene with editor tools (above)
|
||||
2. `play_scene` to start the game
|
||||
3. Use `simulate_key`/`simulate_mouse_click` for input
|
||||
4. `get_game_screenshot` or `capture_frames` to observe results
|
||||
5. `stop_scene` when done
|
||||
|
||||
### Inspecting a project
|
||||
1. `get_project_info` for overview
|
||||
2. `get_scene_tree` for current scene structure
|
||||
3. `read_script` to read code
|
||||
4. `get_node_properties` for specific node details
|
||||
|
||||
### Migrating code properties to inspector
|
||||
When a script hardcodes visual properties (colors, sizes, positions, theme overrides) that should be in the inspector:
|
||||
1. `read_script` to find hardcoded property assignments (e.g. `modulate = Color(...)`, `add_theme_color_override(...)`)
|
||||
2. `get_node_properties` to see current inspector values
|
||||
3. `update_property` to set the same values as node properties in the inspector
|
||||
4. `edit_script` to remove the hardcoded lines from the script
|
||||
5. `save_scene` to persist the inspector changes
|
||||
6. `validate_script` to verify the script still works
|
||||
|
||||
## Formatting Rules
|
||||
|
||||
### execute_editor_script
|
||||
The `code` parameter must be valid GDScript. Use `_mcp_print(value)` to return output.
|
||||
|
||||
```
|
||||
# Correct
|
||||
_mcp_print("hello")
|
||||
|
||||
# Correct - multi-line
|
||||
var nodes = []
|
||||
for child in EditorInterface.get_edited_scene_root().get_children():
|
||||
nodes.append(child.name)
|
||||
_mcp_print(str(nodes))
|
||||
```
|
||||
|
||||
### execute_game_script
|
||||
Same as above but runs inside the running game. Additional rules:
|
||||
- No nested functions (`func` inside `func` is invalid GDScript)
|
||||
- Use `.get("property")` instead of `.property` for safe access
|
||||
- Runs in a temporary node — use `get_tree()` to access the scene tree
|
||||
|
||||
### batch_add_nodes
|
||||
Pass an array of node definitions. Nodes are processed in order, so earlier nodes can be parents for later ones:
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{"type": "Node2D", "name": "Container", "parent_path": "."},
|
||||
{"type": "Sprite2D", "name": "Icon", "parent_path": "Container"},
|
||||
{"type": "Label", "name": "Title", "parent_path": "Container", "properties": {"text": "Hello"}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Prefer inspector properties over code** — When changing visual properties (colors, sizes, theme overrides, transforms, etc.), use `update_property` to set them directly on the node. This keeps values visible in the Godot inspector and easy to tweak. Only use GDScript when the property isn't available in the inspector or needs to be dynamic at runtime.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
1. **Never edit project.godot directly** — Use `set_project_setting` instead. The Godot editor overwrites the file.
|
||||
2. **GDScript type inference** — Use explicit type annotations in for-loops: `for item: String in array` instead of `for item in array`.
|
||||
3. **Reload after script changes** — After `create_script`, call `reload_project` if the script doesn't take effect.
|
||||
4. **Property values as strings** — Properties like position accept string format: `"Vector2(100, 200)"`, `"Color(1, 0, 0, 1)"`.
|
||||
5. **simulate_key duration** — Use short durations (0.3-0.5s) for precise movement. Integer seconds (1, 2, 3) cause overshooting.
|
||||
6. **compare_screenshots** — Pass file paths (`user://screenshot.png`), not base64 data.
|
||||
|
||||
## CLI Mode (Alternative to MCP Tools)
|
||||
|
||||
If MCP tools are unavailable or you have a terminal/bash tool, you can control Godot via the CLI.
|
||||
The CLI requires the server to be built first (`node build/setup.js install` in the server directory).
|
||||
|
||||
```bash
|
||||
# Discover available command groups
|
||||
node /path/to/server/build/cli.js --help
|
||||
|
||||
# Discover commands in a group
|
||||
node /path/to/server/build/cli.js scene --help
|
||||
|
||||
# Discover options for a specific command
|
||||
node /path/to/server/build/cli.js node add --help
|
||||
|
||||
# Execute commands
|
||||
node /path/to/server/build/cli.js project info
|
||||
node /path/to/server/build/cli.js scene tree
|
||||
node /path/to/server/build/cli.js node add --type CharacterBody3D --name Player --parent /root/Main
|
||||
node /path/to/server/build/cli.js script read --path res://player.gd
|
||||
node /path/to/server/build/cli.js scene play
|
||||
node /path/to/server/build/cli.js input key --key W --duration 0.5
|
||||
node /path/to/server/build/cli.js runtime tree
|
||||
```
|
||||
|
||||
**Command groups**: project, scene, node, script, editor, input, runtime
|
||||
|
||||
Always start by running `--help` to discover available commands. Use the CLI when MCP tools are not loaded or when you need to reduce context usage.
|
||||
@@ -0,0 +1,146 @@
|
||||
# Godot MCP Pro - AI Assistant Instructions
|
||||
|
||||
You have access to the Godot MCP Pro toolset for building and testing Godot games through the editor. Follow these rules carefully.
|
||||
|
||||
## Critical: Editor vs Runtime Tools
|
||||
|
||||
Tools are split into two categories. **Using a runtime tool without starting the game will always fail.**
|
||||
|
||||
### Editor Tools (always available)
|
||||
These work on the currently open scene in the Godot editor:
|
||||
- **Scene**: `get_scene_tree`, `create_scene`, `open_scene`, `save_scene`, `delete_scene`, `add_scene_instance`, `get_scene_file_content`, `get_scene_exports`
|
||||
- **Nodes**: `add_node`, `delete_node`, `duplicate_node`, `move_node`, `rename_node`, `update_property`, `get_node_properties`, `add_resource`, `set_anchor_preset`, `connect_signal`, `disconnect_signal`, `get_node_groups`, `set_node_groups`, `find_nodes_in_group`
|
||||
- **Scripts**: `create_script`, `read_script`, `edit_script`, `validate_script`, `attach_script`, `get_open_scripts`, `list_scripts`
|
||||
- **Project**: `get_project_info`, `get_project_settings`, `set_project_setting`, `get_project_statistics`, `get_filesystem_tree`, `get_input_actions`, `set_input_action`
|
||||
- **Editor**: `execute_editor_script`, `get_editor_errors`, `get_output_log`, `get_editor_screenshot`, `clear_output`, `reload_plugin`, `reload_project`
|
||||
- **Resources**: `create_resource`, `read_resource`, `edit_resource`, `get_resource_preview`
|
||||
- **Batch**: `batch_add_nodes`, `batch_set_property`, `find_nodes_by_type`, `find_signal_connections`, `find_node_references`, `get_scene_dependencies`, `cross_scene_set_property`
|
||||
- **3D**: `add_mesh_instance`, `setup_environment`, `setup_lighting`, `setup_camera_3d`, `setup_collision`, `setup_physics_body`, `set_material_3d`, `add_raycast`, `add_gridmap`
|
||||
- **Animation**: `create_animation`, `add_animation_track`, `set_animation_keyframe`, `list_animations`, `get_animation_info`, `remove_animation`
|
||||
- **Animation Tree**: `create_animation_tree`, `get_animation_tree_structure`, `add_state_machine_state`, `add_state_machine_transition`, `remove_state_machine_state`, `remove_state_machine_transition`, `set_blend_tree_node`, `set_tree_parameter`
|
||||
- **Audio**: `add_audio_player`, `add_audio_bus`, `add_audio_bus_effect`, `set_audio_bus`, `get_audio_bus_layout`, `get_audio_info`
|
||||
- **Navigation**: `setup_navigation_region`, `setup_navigation_agent`, `bake_navigation_mesh`, `set_navigation_layers`, `get_navigation_info`
|
||||
- **Particles**: `create_particles`, `set_particle_material`, `set_particle_color_gradient`, `apply_particle_preset`, `get_particle_info`
|
||||
- **Physics**: `get_physics_layers`, `set_physics_layers`, `get_collision_info`
|
||||
- **Shader**: `create_shader`, `read_shader`, `edit_shader`, `assign_shader_material`, `get_shader_params`, `set_shader_param`
|
||||
- **Theme**: `create_theme`, `get_theme_info`, `set_theme_color`, `set_theme_font_size`, `set_theme_constant`, `set_theme_stylebox`
|
||||
- **Tilemap**: `tilemap_get_info`, `tilemap_set_cell`, `tilemap_get_cell`, `tilemap_fill_rect`, `tilemap_clear`, `tilemap_get_used_cells`
|
||||
- **Export**: `list_export_presets`, `get_export_info`, `export_project`
|
||||
- **Analysis**: `analyze_scene_complexity`, `analyze_signal_flow`, `detect_circular_dependencies`, `find_unused_resources`, `get_performance_monitors`, `search_files`, `search_in_files`, `find_script_references`
|
||||
- **Profiling**: `get_editor_performance`
|
||||
|
||||
### Runtime Tools (require `play_scene` first)
|
||||
You MUST call `play_scene` before using any of these. They interact with the running game:
|
||||
- **Game State**: `get_game_scene_tree`, `get_game_node_properties`, `set_game_node_property`, `execute_game_script`, `get_game_screenshot`, `get_autoload`, `find_nodes_by_script`
|
||||
- **Input Simulation**: `simulate_key`, `simulate_mouse_click`, `simulate_mouse_move`, `simulate_action`, `simulate_sequence`
|
||||
- **Capture/Recording**: `capture_frames`, `record_frames`, `monitor_properties`, `start_recording`, `stop_recording`, `replay_recording`, `batch_get_properties`
|
||||
- **UI Interaction**: `find_ui_elements`, `click_button_by_text`, `wait_for_node`, `find_nearby_nodes`, `navigate_to`, `move_to`
|
||||
- **Testing**: `run_test_scenario`, `assert_node_state`, `assert_screen_text`, `run_stress_test`, `get_test_report`
|
||||
- **Screenshots**: `get_game_screenshot`, `compare_screenshots`
|
||||
- **Control**: `play_scene`, `stop_scene`
|
||||
|
||||
## Workflow Patterns
|
||||
|
||||
### Building a scene from scratch
|
||||
1. `create_scene` or `open_scene`
|
||||
2. Use `add_node` or `batch_add_nodes` to add nodes
|
||||
3. `create_script` + `attach_script` for behavior
|
||||
4. `save_scene`
|
||||
|
||||
### Testing gameplay
|
||||
1. Build scene with editor tools (above)
|
||||
2. `play_scene` to start the game
|
||||
3. Use `simulate_key`/`simulate_mouse_click` for input
|
||||
4. `get_game_screenshot` or `capture_frames` to observe results
|
||||
5. `stop_scene` when done
|
||||
|
||||
### Inspecting a project
|
||||
1. `get_project_info` for overview
|
||||
2. `get_scene_tree` for current scene structure
|
||||
3. `read_script` to read code
|
||||
4. `get_node_properties` for specific node details
|
||||
|
||||
### Migrating code properties to inspector
|
||||
When a script hardcodes visual properties (colors, sizes, positions, theme overrides) that should be in the inspector:
|
||||
1. `read_script` to find hardcoded property assignments (e.g. `modulate = Color(...)`, `add_theme_color_override(...)`)
|
||||
2. `get_node_properties` to see current inspector values
|
||||
3. `update_property` to set the same values as node properties in the inspector
|
||||
4. `edit_script` to remove the hardcoded lines from the script
|
||||
5. `save_scene` to persist the inspector changes
|
||||
6. `validate_script` to verify the script still works
|
||||
|
||||
## Formatting Rules
|
||||
|
||||
### execute_editor_script
|
||||
The `code` parameter must be valid GDScript. Use `_mcp_print(value)` to return output.
|
||||
|
||||
```
|
||||
# Correct
|
||||
_mcp_print("hello")
|
||||
|
||||
# Correct - multi-line
|
||||
var nodes = []
|
||||
for child in EditorInterface.get_edited_scene_root().get_children():
|
||||
nodes.append(child.name)
|
||||
_mcp_print(str(nodes))
|
||||
```
|
||||
|
||||
### execute_game_script
|
||||
Same as above but runs inside the running game. Additional rules:
|
||||
- No nested functions (`func` inside `func` is invalid GDScript)
|
||||
- Use `.get("property")` instead of `.property` for safe access
|
||||
- Runs in a temporary node — use `get_tree()` to access the scene tree
|
||||
|
||||
### batch_add_nodes
|
||||
Pass an array of node definitions. Nodes are processed in order, so earlier nodes can be parents for later ones:
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{"type": "Node2D", "name": "Container", "parent_path": "."},
|
||||
{"type": "Sprite2D", "name": "Icon", "parent_path": "Container"},
|
||||
{"type": "Label", "name": "Title", "parent_path": "Container", "properties": {"text": "Hello"}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Prefer inspector properties over code** — When changing visual properties (colors, sizes, theme overrides, transforms, etc.), use `update_property` to set them directly on the node. This keeps values visible in the Godot inspector and easy to tweak. Only use GDScript when the property isn't available in the inspector or needs to be dynamic at runtime.
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
1. **Never edit project.godot directly** — Use `set_project_setting` instead. The Godot editor overwrites the file.
|
||||
2. **GDScript type inference** — Use explicit type annotations in for-loops: `for item: String in array` instead of `for item in array`.
|
||||
3. **Reload after script changes** — After `create_script`, call `reload_project` if the script doesn't take effect.
|
||||
4. **Property values as strings** — Properties like position accept string format: `"Vector2(100, 200)"`, `"Color(1, 0, 0, 1)"`.
|
||||
5. **simulate_key duration** — Use short durations (0.3-0.5s) for precise movement. Integer seconds (1, 2, 3) cause overshooting.
|
||||
6. **compare_screenshots** — Pass file paths (`user://screenshot.png`), not base64 data.
|
||||
|
||||
## CLI Mode (Alternative to MCP Tools)
|
||||
|
||||
If MCP tools are unavailable or you have a terminal/bash tool, you can control Godot via the CLI.
|
||||
The CLI requires the server to be built first (`node build/setup.js install` in the server directory).
|
||||
|
||||
```bash
|
||||
# Discover available command groups
|
||||
node /path/to/server/build/cli.js --help
|
||||
|
||||
# Discover commands in a group
|
||||
node /path/to/server/build/cli.js scene --help
|
||||
|
||||
# Discover options for a specific command
|
||||
node /path/to/server/build/cli.js node add --help
|
||||
|
||||
# Execute commands
|
||||
node /path/to/server/build/cli.js project info
|
||||
node /path/to/server/build/cli.js scene tree
|
||||
node /path/to/server/build/cli.js node add --type CharacterBody3D --name Player --parent /root/Main
|
||||
node /path/to/server/build/cli.js script read --path res://player.gd
|
||||
node /path/to/server/build/cli.js scene play
|
||||
node /path/to/server/build/cli.js input key --key W --duration 0.5
|
||||
node /path/to/server/build/cli.js runtime tree
|
||||
```
|
||||
|
||||
**Command groups**: project, scene, node, script, editor, input, runtime
|
||||
|
||||
Always start by running `--help` to discover available commands. Use the CLI when MCP tools are not loaded or when you need to reduce context usage.
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* godot-cli — Command-line interface for Godot MCP Pro
|
||||
*
|
||||
* Connects directly to the Godot editor plugin via WebSocket (JSON-RPC 2.0).
|
||||
* Designed for LLMs that can use bash/terminal tools but have tight MCP tool limits.
|
||||
* Progressive disclosure via --help at each command level.
|
||||
*/
|
||||
export {};
|
||||
//# sourceMappingURL=cli.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;;;;GAMG"}
|
||||
@@ -0,0 +1,705 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* godot-cli — Command-line interface for Godot MCP Pro
|
||||
*
|
||||
* Connects directly to the Godot editor plugin via WebSocket (JSON-RPC 2.0).
|
||||
* Designed for LLMs that can use bash/terminal tools but have tight MCP tool limits.
|
||||
* Progressive disclosure via --help at each command level.
|
||||
*/
|
||||
import { WebSocketServer } from "ws";
|
||||
import { randomUUID } from "crypto";
|
||||
import { createServer } from "net";
|
||||
const BASE_PORT = 6510;
|
||||
const MAX_PORT = 6514;
|
||||
const CONNECT_TIMEOUT_MS = 10000;
|
||||
const COMMAND_TIMEOUT_MS = 30000;
|
||||
const COMMANDS = {
|
||||
project: {
|
||||
description: "Project info, files, and settings",
|
||||
commands: {
|
||||
info: {
|
||||
description: "Get project metadata (name, version, viewport, renderer, autoloads)",
|
||||
method: "get_project_info",
|
||||
},
|
||||
files: {
|
||||
description: "List project file/directory tree",
|
||||
method: "get_filesystem_tree",
|
||||
args: {
|
||||
path: { description: "Root path (default: res://)" },
|
||||
filter: { description: "Glob filter (e.g. '*.gd', '*.tscn')" },
|
||||
},
|
||||
},
|
||||
search: {
|
||||
description: "Search for files by name pattern",
|
||||
method: "search_files",
|
||||
args: {
|
||||
query: { description: "Search query (fuzzy match or glob)", required: true },
|
||||
path: { description: "Directory to search in" },
|
||||
file_type: { description: "Filter by extension (e.g. 'gd', 'tscn')" },
|
||||
},
|
||||
},
|
||||
grep: {
|
||||
description: "Search inside file contents",
|
||||
method: "search_in_files",
|
||||
args: {
|
||||
query: { description: "Text/regex pattern", required: true },
|
||||
path: { description: "Directory to search in" },
|
||||
file_type: { description: "File extension filter (e.g. 'gd', 'tscn')" },
|
||||
},
|
||||
},
|
||||
"get-setting": {
|
||||
description: "Get project settings",
|
||||
method: "get_project_settings",
|
||||
args: {
|
||||
category: { description: "Settings category filter" },
|
||||
},
|
||||
},
|
||||
"set-setting": {
|
||||
description: "Set a project setting",
|
||||
method: "set_project_setting",
|
||||
args: {
|
||||
setting: { description: "Setting path (e.g. display/window/size/viewport_width)", required: true },
|
||||
value: { description: "Value to set", required: true },
|
||||
},
|
||||
mapArgs: (p) => ({ setting: p.setting, value: autoType(p.value) }),
|
||||
},
|
||||
},
|
||||
},
|
||||
scene: {
|
||||
description: "Scene tree and scene management",
|
||||
commands: {
|
||||
tree: {
|
||||
description: "Get the current scene tree",
|
||||
method: "get_scene_tree",
|
||||
args: {
|
||||
max_depth: { description: "Maximum depth to display", type: "number" },
|
||||
},
|
||||
mapArgs: (p) => (p.max_depth ? { max_depth: parseInt(p.max_depth) } : {}),
|
||||
},
|
||||
create: {
|
||||
description: "Create a new scene with a root node",
|
||||
method: "create_scene",
|
||||
args: {
|
||||
path: { description: "Scene path (e.g. res://scenes/player.tscn)", required: true },
|
||||
root_type: { description: "Root node type (default: Node2D)" },
|
||||
root_name: { description: "Root node name" },
|
||||
},
|
||||
},
|
||||
open: {
|
||||
description: "Open a scene in the editor",
|
||||
method: "open_scene",
|
||||
args: {
|
||||
path: { description: "Scene path to open", required: true },
|
||||
},
|
||||
},
|
||||
save: {
|
||||
description: "Save the current scene",
|
||||
method: "save_scene",
|
||||
args: {
|
||||
path: { description: "Optional path to save as" },
|
||||
},
|
||||
},
|
||||
play: {
|
||||
description: "Run the current/specified scene",
|
||||
method: "play_scene",
|
||||
args: {
|
||||
mode: { description: "'main' (default), 'current', or a scene path" },
|
||||
},
|
||||
},
|
||||
stop: {
|
||||
description: "Stop the running scene",
|
||||
method: "stop_scene",
|
||||
},
|
||||
content: {
|
||||
description: "Get scene file content (tscn format parsed)",
|
||||
method: "get_scene_file_content",
|
||||
args: {
|
||||
path: { description: "Scene file path", required: true },
|
||||
},
|
||||
},
|
||||
exports: {
|
||||
description: "Get exported variables of a scene",
|
||||
method: "get_scene_exports",
|
||||
args: {
|
||||
path: { description: "Scene file path", required: true },
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
description: "Delete a scene file",
|
||||
method: "delete_scene",
|
||||
args: {
|
||||
path: { description: "Scene file path to delete", required: true },
|
||||
},
|
||||
},
|
||||
instance: {
|
||||
description: "Add a scene instance as child node",
|
||||
method: "add_scene_instance",
|
||||
args: {
|
||||
scene_path: { description: "Path to .tscn file to instance", required: true },
|
||||
parent_path: { description: "Parent node path (default: selected/root)" },
|
||||
name: { description: "Instance name" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
node: {
|
||||
description: "Add, modify, and delete scene nodes",
|
||||
commands: {
|
||||
add: {
|
||||
description: "Add a new node to the scene",
|
||||
method: "add_node",
|
||||
args: {
|
||||
type: { description: "Node type (e.g. CharacterBody3D, Sprite2D)", required: true },
|
||||
name: { description: "Node name" },
|
||||
parent_path: { description: "Parent node path (default: root '.')" },
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
description: "Delete a node from the scene",
|
||||
method: "delete_node",
|
||||
args: {
|
||||
node_path: { description: "Node path to delete", required: true },
|
||||
},
|
||||
},
|
||||
get: {
|
||||
description: "Get all properties of a node",
|
||||
method: "get_node_properties",
|
||||
args: {
|
||||
node_path: { description: "Node path", required: true },
|
||||
},
|
||||
},
|
||||
set: {
|
||||
description: "Set a property on a node",
|
||||
method: "update_property",
|
||||
args: {
|
||||
node_path: { description: "Node path", required: true },
|
||||
property: { description: "Property name", required: true },
|
||||
value: { description: "Value to set", required: true },
|
||||
},
|
||||
mapArgs: (p) => ({ node_path: p.node_path, property: p.property, value: autoType(p.value) }),
|
||||
},
|
||||
duplicate: {
|
||||
description: "Duplicate a node",
|
||||
method: "duplicate_node",
|
||||
args: {
|
||||
node_path: { description: "Node path to duplicate", required: true },
|
||||
name: { description: "Name for the duplicate" },
|
||||
},
|
||||
},
|
||||
move: {
|
||||
description: "Move/reparent a node",
|
||||
method: "move_node",
|
||||
args: {
|
||||
node_path: { description: "Node path to move", required: true },
|
||||
new_parent_path: { description: "New parent path", required: true },
|
||||
},
|
||||
},
|
||||
rename: {
|
||||
description: "Rename a node",
|
||||
method: "rename_node",
|
||||
args: {
|
||||
node_path: { description: "Node path", required: true },
|
||||
new_name: { description: "New name", required: true },
|
||||
},
|
||||
},
|
||||
connect: {
|
||||
description: "Connect a signal between nodes",
|
||||
method: "connect_signal",
|
||||
args: {
|
||||
source_path: { description: "Source node path", required: true },
|
||||
signal_name: { description: "Signal name", required: true },
|
||||
target_path: { description: "Target node path", required: true },
|
||||
method_name: { description: "Target method name", required: true },
|
||||
},
|
||||
},
|
||||
groups: {
|
||||
description: "Get groups a node belongs to",
|
||||
method: "get_node_groups",
|
||||
args: {
|
||||
node_path: { description: "Node path", required: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
script: {
|
||||
description: "Read, create, and edit GDScript/C# files",
|
||||
commands: {
|
||||
read: {
|
||||
description: "Read a script file",
|
||||
method: "read_script",
|
||||
args: {
|
||||
path: { description: "Script path (e.g. res://player.gd)", required: true },
|
||||
},
|
||||
},
|
||||
create: {
|
||||
description: "Create a new script file (.gd or .cs only)",
|
||||
method: "create_script",
|
||||
args: {
|
||||
path: { description: "Script path", required: true },
|
||||
content: { description: "Script content", required: true },
|
||||
base_type: { description: "Base class (default: Node)" },
|
||||
force: { description: "Override open-script-editor guard" },
|
||||
},
|
||||
mapArgs: (p) => {
|
||||
const r = { path: p.path, content: p.content };
|
||||
if (p.base_type)
|
||||
r.base_type = p.base_type;
|
||||
if (p.force !== undefined)
|
||||
r.force = p.force === "true";
|
||||
return r;
|
||||
},
|
||||
},
|
||||
edit: {
|
||||
description: "Edit an existing script (full replace or 1-based inclusive line range)",
|
||||
method: "edit_script",
|
||||
args: {
|
||||
path: { description: "Script path", required: true },
|
||||
content: { description: "New content", required: true },
|
||||
start_line: { description: "Start line for partial edit (1-based inclusive)", type: "number" },
|
||||
end_line: { description: "End line for partial edit (1-based inclusive)", type: "number" },
|
||||
force: { description: "Override open-script-editor guard" },
|
||||
},
|
||||
mapArgs: (p) => {
|
||||
const r = { path: p.path, content: p.content };
|
||||
if (p.start_line)
|
||||
r.start_line = parseInt(p.start_line);
|
||||
if (p.end_line)
|
||||
r.end_line = parseInt(p.end_line);
|
||||
if (p.force !== undefined)
|
||||
r.force = p.force === "true";
|
||||
return r;
|
||||
},
|
||||
},
|
||||
attach: {
|
||||
description: "Attach a script to a node",
|
||||
method: "attach_script",
|
||||
args: {
|
||||
node_path: { description: "Node path", required: true },
|
||||
script_path: { description: "Script path", required: true },
|
||||
},
|
||||
},
|
||||
validate: {
|
||||
description: "Validate a GDScript for errors",
|
||||
method: "validate_script",
|
||||
args: {
|
||||
path: { description: "Script path to validate", required: true },
|
||||
},
|
||||
},
|
||||
list: {
|
||||
description: "List all scripts in the project",
|
||||
method: "list_scripts",
|
||||
},
|
||||
},
|
||||
},
|
||||
editor: {
|
||||
description: "Editor state, errors, screenshots, and utilities",
|
||||
commands: {
|
||||
errors: {
|
||||
description: "Get current editor errors/warnings",
|
||||
method: "get_editor_errors",
|
||||
},
|
||||
log: {
|
||||
description: "Get editor output log",
|
||||
method: "get_output_log",
|
||||
args: {
|
||||
lines: { description: "Number of lines (default: 50)", type: "number" },
|
||||
},
|
||||
mapArgs: (p) => (p.lines ? { lines: parseInt(p.lines) } : {}),
|
||||
},
|
||||
screenshot: {
|
||||
description: "Take a screenshot of the running game",
|
||||
method: "get_game_screenshot",
|
||||
},
|
||||
"editor-screenshot": {
|
||||
description: "Take a screenshot of the editor",
|
||||
method: "get_editor_screenshot",
|
||||
},
|
||||
exec: {
|
||||
description: "Execute an editor script (GDScript in editor context)",
|
||||
method: "execute_editor_script",
|
||||
args: {
|
||||
code: { description: "GDScript code to execute", required: true },
|
||||
},
|
||||
},
|
||||
signals: {
|
||||
description: "Get signals of a node type",
|
||||
method: "get_signals",
|
||||
args: {
|
||||
node_path: { description: "Node path", required: true },
|
||||
},
|
||||
},
|
||||
reload: {
|
||||
description: "Reload the project",
|
||||
method: "reload_project",
|
||||
},
|
||||
},
|
||||
},
|
||||
input: {
|
||||
description: "Simulate keyboard, mouse, and input actions",
|
||||
commands: {
|
||||
key: {
|
||||
description: "Simulate a key press in the running game",
|
||||
method: "simulate_key",
|
||||
args: {
|
||||
key: { description: "Key name (e.g. W, A, S, D, Space)", required: true },
|
||||
duration: { description: "Hold duration in seconds", type: "number" },
|
||||
pressed: { description: "true=press, false=release" },
|
||||
},
|
||||
mapArgs: (p) => {
|
||||
const r = { key: p.key };
|
||||
if (p.duration)
|
||||
r.duration = parseFloat(p.duration);
|
||||
if (p.pressed !== undefined)
|
||||
r.pressed = p.pressed === "true";
|
||||
return r;
|
||||
},
|
||||
},
|
||||
click: {
|
||||
description: "Simulate a mouse click in the running game",
|
||||
method: "simulate_mouse_click",
|
||||
args: {
|
||||
x: { description: "X coordinate", required: true, type: "number" },
|
||||
y: { description: "Y coordinate", required: true, type: "number" },
|
||||
button: { description: "Mouse button: left, right, or middle (default: left)" },
|
||||
},
|
||||
mapArgs: (p) => {
|
||||
const buttonMap = { left: 1, right: 2, middle: 3 };
|
||||
const r = { x: parseInt(p.x), y: parseInt(p.y) };
|
||||
if (p.button)
|
||||
r.button = buttonMap[p.button.toLowerCase()] ?? (parseInt(p.button) || 1);
|
||||
return r;
|
||||
},
|
||||
},
|
||||
action: {
|
||||
description: "Simulate an input action (as defined in Input Map)",
|
||||
method: "simulate_action",
|
||||
args: {
|
||||
action: { description: "Action name (e.g. ui_accept, move_left)", required: true },
|
||||
pressed: { description: "true=press, false=release" },
|
||||
duration: { description: "Hold duration in seconds", type: "number" },
|
||||
},
|
||||
mapArgs: (p) => {
|
||||
const r = { action: p.action };
|
||||
if (p.pressed !== undefined)
|
||||
r.pressed = p.pressed === "true";
|
||||
if (p.duration)
|
||||
r.duration = parseFloat(p.duration);
|
||||
return r;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
description: "List all configured input actions",
|
||||
method: "get_input_actions",
|
||||
},
|
||||
},
|
||||
},
|
||||
runtime: {
|
||||
description: "Inspect and control the running game",
|
||||
commands: {
|
||||
tree: {
|
||||
description: "Get the running game's scene tree",
|
||||
method: "get_game_scene_tree",
|
||||
args: {
|
||||
max_depth: { description: "Max depth (-1 for unlimited)", type: "number" },
|
||||
},
|
||||
mapArgs: (p) => {
|
||||
const r = {};
|
||||
if (p.max_depth)
|
||||
r.max_depth = parseInt(p.max_depth);
|
||||
return r;
|
||||
},
|
||||
},
|
||||
get: {
|
||||
description: "Get properties of a node in the running game",
|
||||
method: "get_game_node_properties",
|
||||
args: {
|
||||
node_path: { description: "Node path", required: true },
|
||||
properties: { description: "Comma-separated property names (default: all)" },
|
||||
},
|
||||
mapArgs: (p) => {
|
||||
const r = { node_path: p.node_path };
|
||||
if (p.properties)
|
||||
r.properties = p.properties.split(",").map(s => s.trim());
|
||||
return r;
|
||||
},
|
||||
},
|
||||
set: {
|
||||
description: "Set a property on a running game node",
|
||||
method: "set_game_node_property",
|
||||
args: {
|
||||
node_path: { description: "Node path", required: true },
|
||||
property: { description: "Property name", required: true },
|
||||
value: { description: "Value to set", required: true },
|
||||
},
|
||||
mapArgs: (p) => ({ node_path: p.node_path, property: p.property, value: autoType(p.value) }),
|
||||
},
|
||||
exec: {
|
||||
description: "Execute GDScript in the running game",
|
||||
method: "execute_game_script",
|
||||
args: {
|
||||
code: { description: "GDScript code", required: true },
|
||||
node_path: { description: "Node context (default: /root)" },
|
||||
},
|
||||
},
|
||||
ui: {
|
||||
description: "Find UI elements (buttons, labels, etc.) in the running game",
|
||||
method: "find_ui_elements",
|
||||
args: {
|
||||
type_filter: { description: "Filter by type (Button, Label, etc.)" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
// ─── Argument parsing ─────────────────────────────────────────────────
|
||||
function autoType(value) {
|
||||
if (value === "true")
|
||||
return true;
|
||||
if (value === "false")
|
||||
return false;
|
||||
if (value === "null")
|
||||
return null;
|
||||
const num = Number(value);
|
||||
if (!isNaN(num) && value.trim() !== "")
|
||||
return num;
|
||||
// Try JSON for arrays/objects
|
||||
if ((value.startsWith("[") || value.startsWith("{")) && (value.endsWith("]") || value.endsWith("}"))) {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
catch { /* fall through */ }
|
||||
}
|
||||
return value;
|
||||
}
|
||||
function parseArgs(argv) {
|
||||
const positional = [];
|
||||
const flags = {};
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const arg = argv[i];
|
||||
if (arg.startsWith("--")) {
|
||||
const key = arg.slice(2);
|
||||
const next = argv[i + 1];
|
||||
if (next && !next.startsWith("--")) {
|
||||
flags[key] = next;
|
||||
i++;
|
||||
}
|
||||
else {
|
||||
flags[key] = "true";
|
||||
}
|
||||
}
|
||||
else {
|
||||
positional.push(arg);
|
||||
}
|
||||
}
|
||||
return { positional, flags };
|
||||
}
|
||||
// ─── Help formatting ──────────────────────────────────────────────────
|
||||
function showMainHelp() {
|
||||
console.log(`godot-cli — Control Godot editor from the command line
|
||||
|
||||
Usage: godot-cli <group> <command> [options]
|
||||
|
||||
Groups:`);
|
||||
for (const [name, group] of Object.entries(COMMANDS)) {
|
||||
console.log(` ${name.padEnd(12)} ${group.description}`);
|
||||
}
|
||||
console.log(`
|
||||
Options:
|
||||
--port <N> Godot WebSocket port (default: auto-detect 6510-6514)
|
||||
--help Show help for a group or command
|
||||
|
||||
Examples:
|
||||
godot-cli project info
|
||||
godot-cli scene tree
|
||||
godot-cli node add --type CharacterBody3D --name Player
|
||||
godot-cli script read --path res://player.gd
|
||||
godot-cli scene play
|
||||
godot-cli input key --key W --duration 0.5`);
|
||||
}
|
||||
function showGroupHelp(groupName, group) {
|
||||
console.log(`godot-cli ${groupName} — ${group.description}
|
||||
|
||||
Commands:`);
|
||||
for (const [name, cmd] of Object.entries(group.commands)) {
|
||||
console.log(` ${name.padEnd(18)} ${cmd.description}`);
|
||||
}
|
||||
console.log(`\nUse: godot-cli ${groupName} <command> --help for details`);
|
||||
}
|
||||
function showCommandHelp(groupName, cmdName, cmd) {
|
||||
console.log(`godot-cli ${groupName} ${cmdName} — ${cmd.description}`);
|
||||
if (cmd.args && Object.keys(cmd.args).length > 0) {
|
||||
console.log(`\nOptions:`);
|
||||
for (const [name, arg] of Object.entries(cmd.args)) {
|
||||
const req = arg.required ? " (required)" : "";
|
||||
console.log(` --${name.padEnd(16)} ${arg.description}${req}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// ─── WebSocket connection ─────────────────────────────────────────────
|
||||
// The Godot plugin is a WebSocket CLIENT that connects to servers on ports 6505-6514.
|
||||
// The CLI starts a temporary WebSocket SERVER on an available port and waits for
|
||||
// the Godot plugin to connect (it polls every 3 seconds).
|
||||
function isPortFree(port) {
|
||||
return new Promise((resolve) => {
|
||||
const server = createServer();
|
||||
server.once("error", () => resolve(false));
|
||||
server.once("listening", () => {
|
||||
server.close(() => resolve(true));
|
||||
});
|
||||
server.listen(port, "127.0.0.1");
|
||||
});
|
||||
}
|
||||
async function findFreePort(preferredPort) {
|
||||
if (preferredPort) {
|
||||
if (await isPortFree(preferredPort))
|
||||
return preferredPort;
|
||||
return null;
|
||||
}
|
||||
for (let p = BASE_PORT; p <= MAX_PORT; p++) {
|
||||
if (await isPortFree(p))
|
||||
return p;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Start a WebSocket server and wait for the Godot plugin to connect.
|
||||
* Returns the connected client WebSocket and the server (for cleanup).
|
||||
*/
|
||||
function waitForGodot(port) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const wss = new WebSocketServer({ port, host: "127.0.0.1" });
|
||||
const timeout = setTimeout(() => {
|
||||
wss.close();
|
||||
reject(new Error(`Godot plugin did not connect within ${CONNECT_TIMEOUT_MS / 1000}s.\n` +
|
||||
"Make sure the Godot editor is running with the MCP plugin enabled."));
|
||||
}, CONNECT_TIMEOUT_MS);
|
||||
wss.on("error", (err) => {
|
||||
clearTimeout(timeout);
|
||||
reject(err);
|
||||
});
|
||||
wss.on("connection", (ws) => {
|
||||
clearTimeout(timeout);
|
||||
resolve({ client: ws, wss });
|
||||
});
|
||||
});
|
||||
}
|
||||
function sendCommand(ws, method, params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = randomUUID();
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error(`Command '${method}' timed out after ${COMMAND_TIMEOUT_MS}ms`));
|
||||
}, COMMAND_TIMEOUT_MS);
|
||||
const handler = (data) => {
|
||||
let msg;
|
||||
try {
|
||||
msg = JSON.parse(data.toString());
|
||||
}
|
||||
catch {
|
||||
return;
|
||||
}
|
||||
// Ignore ping/pong
|
||||
if (msg.method === "pong" || msg.method === "ping")
|
||||
return;
|
||||
if (msg.id !== id)
|
||||
return;
|
||||
clearTimeout(timeout);
|
||||
ws.off("message", handler);
|
||||
if (msg.error) {
|
||||
reject(new Error(`Godot error: ${msg.error.message || JSON.stringify(msg.error)}`));
|
||||
}
|
||||
else {
|
||||
resolve(msg.result);
|
||||
}
|
||||
};
|
||||
ws.on("message", handler);
|
||||
ws.send(JSON.stringify({ jsonrpc: "2.0", method, params, id }));
|
||||
});
|
||||
}
|
||||
// ─── Main ─────────────────────────────────────────────────────────────
|
||||
async function main() {
|
||||
const userArgs = process.argv.slice(2);
|
||||
const { positional, flags } = parseArgs(userArgs);
|
||||
// Global --help
|
||||
if (positional.length === 0 || flags.help === "true" && positional.length === 0) {
|
||||
showMainHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
const groupName = positional[0];
|
||||
const group = COMMANDS[groupName];
|
||||
if (!group) {
|
||||
console.error(`Unknown group: ${groupName}`);
|
||||
showMainHelp();
|
||||
process.exit(1);
|
||||
}
|
||||
// Group-level --help
|
||||
if (positional.length === 1 || (flags.help === "true" && positional.length === 1)) {
|
||||
showGroupHelp(groupName, group);
|
||||
process.exit(0);
|
||||
}
|
||||
const cmdName = positional[1];
|
||||
const cmd = group.commands[cmdName];
|
||||
if (!cmd) {
|
||||
console.error(`Unknown command: ${groupName} ${cmdName}`);
|
||||
showGroupHelp(groupName, group);
|
||||
process.exit(1);
|
||||
}
|
||||
// Command-level --help
|
||||
if (flags.help === "true") {
|
||||
showCommandHelp(groupName, cmdName, cmd);
|
||||
process.exit(0);
|
||||
}
|
||||
// Validate required args
|
||||
if (cmd.args) {
|
||||
for (const [name, arg] of Object.entries(cmd.args)) {
|
||||
if (arg.required && !flags[name]) {
|
||||
console.error(`Missing required option: --${name}`);
|
||||
showCommandHelp(groupName, cmdName, cmd);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Build params
|
||||
const params = cmd.mapArgs ? cmd.mapArgs(flags) : { ...flags };
|
||||
// Remove internal flags
|
||||
delete params.port;
|
||||
delete params.help;
|
||||
// Connect and execute
|
||||
const preferredPort = flags.port ? parseInt(flags.port) : undefined;
|
||||
const port = await findFreePort(preferredPort);
|
||||
if (!port) {
|
||||
console.error(`No free ports in range ${BASE_PORT}-${MAX_PORT}.\n` +
|
||||
"All ports are occupied by MCP server instances.");
|
||||
process.exit(1);
|
||||
}
|
||||
let client;
|
||||
let wss;
|
||||
try {
|
||||
process.stderr.write(`Waiting for Godot on port ${port}...`);
|
||||
({ client, wss } = await waitForGodot(port));
|
||||
process.stderr.write(" connected!\n");
|
||||
}
|
||||
catch (err) {
|
||||
process.stderr.write("\n");
|
||||
console.error(err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
try {
|
||||
const result = await sendCommand(client, cmd.method, params);
|
||||
if (result !== undefined && result !== null) {
|
||||
console.log(typeof result === "string" ? result : JSON.stringify(result, null, 2));
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
finally {
|
||||
client.close();
|
||||
wss.close();
|
||||
}
|
||||
}
|
||||
main().catch((err) => {
|
||||
console.error("Fatal:", err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
//# sourceMappingURL=cli.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,29 @@
|
||||
export declare class GodotConnection {
|
||||
private wss;
|
||||
private client;
|
||||
private port;
|
||||
private fixedPort;
|
||||
private basePort;
|
||||
private maxPort;
|
||||
private pendingRequests;
|
||||
private heartbeatTimer;
|
||||
private lastPongAt;
|
||||
constructor(port?: number, fixedPort?: boolean, options?: {
|
||||
basePort?: number;
|
||||
maxPort?: number;
|
||||
});
|
||||
/** Start WebSocket server, retrying on the next port if the first bind races. */
|
||||
connect(): Promise<void>;
|
||||
/** Try to bind a single WebSocketServer. Resolves once 'listening' fires, rejects on bind error. */
|
||||
private bindWebSocketServer;
|
||||
private attachConnectionHandler;
|
||||
disconnect(): void;
|
||||
isConnected(): boolean;
|
||||
getPort(): number;
|
||||
sendCommand(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
||||
private handleMessage;
|
||||
private rejectAllPending;
|
||||
private startHeartbeat;
|
||||
private stopHeartbeat;
|
||||
}
|
||||
//# sourceMappingURL=godot-connection.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"godot-connection.d.ts","sourceRoot":"","sources":["../src/godot-connection.ts"],"names":[],"mappings":"AAoBA,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAAgC;IAC3C,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,eAAe,CAA0C;IACjE,OAAO,CAAC,cAAc,CAA+C;IACrE,OAAO,CAAC,UAAU,CAAa;gBAG7B,IAAI,GAAE,MAAkB,EACxB,SAAS,GAAE,OAAe,EAC1B,OAAO,GAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAO;IAQvD,iFAAiF;IAC3E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA2C9B,oGAAoG;IACpG,OAAO,CAAC,mBAAmB;IAwB3B,OAAO,CAAC,uBAAuB;IAsC/B,UAAU,IAAI,IAAI;IAalB,WAAW,IAAI,OAAO;IAItB,OAAO,IAAI,MAAM;IAIX,WAAW,CACf,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,OAAO,CAAC,OAAO,CAAC;IA8BnB,OAAO,CAAC,aAAa;IA6CrB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,cAAc;IA4BtB,OAAO,CAAC,aAAa;CAMtB"}
|
||||
@@ -0,0 +1,226 @@
|
||||
import { WebSocketServer, WebSocket } from "ws";
|
||||
import { randomUUID } from "crypto";
|
||||
import { GodotConnectionError, GodotCommandError, TimeoutError, } from "./utils/errors.js";
|
||||
const BASE_PORT = 6505;
|
||||
const MAX_PORT = 6509;
|
||||
const COMMAND_TIMEOUT_MS = 30000;
|
||||
const HEARTBEAT_INTERVAL_MS = 10000;
|
||||
const HEARTBEAT_TIMEOUT_MS = HEARTBEAT_INTERVAL_MS * 3;
|
||||
const TCP_KEEPALIVE_DELAY_MS = 5000;
|
||||
export class GodotConnection {
|
||||
wss = null;
|
||||
client = null;
|
||||
port;
|
||||
fixedPort;
|
||||
basePort;
|
||||
maxPort;
|
||||
pendingRequests = new Map();
|
||||
heartbeatTimer = null;
|
||||
lastPongAt = 0;
|
||||
constructor(port = BASE_PORT, fixedPort = false, options = {}) {
|
||||
this.port = port;
|
||||
this.fixedPort = fixedPort;
|
||||
this.basePort = options.basePort ?? BASE_PORT;
|
||||
this.maxPort = options.maxPort ?? MAX_PORT;
|
||||
}
|
||||
/** Start WebSocket server, retrying on the next port if the first bind races. */
|
||||
async connect() {
|
||||
if (this.wss)
|
||||
return;
|
||||
const candidates = this.fixedPort
|
||||
? [this.port]
|
||||
: Array.from({ length: this.maxPort - this.basePort + 1 }, (_, i) => this.basePort + i);
|
||||
let lastError = null;
|
||||
for (const port of candidates) {
|
||||
try {
|
||||
const wss = await this.bindWebSocketServer(port);
|
||||
this.wss = wss;
|
||||
this.port = port;
|
||||
this.attachConnectionHandler(wss);
|
||||
console.error(`[MCP] WebSocket server listening on ws://127.0.0.1:${port}`);
|
||||
return;
|
||||
}
|
||||
catch (err) {
|
||||
lastError = err;
|
||||
// EADDRINUSE means another MCP server (likely a parallel Claude session)
|
||||
// won the bind race. Silently try the next port. Other errors are
|
||||
// logged so we don't swallow real config problems.
|
||||
if (err.code !== "EADDRINUSE") {
|
||||
console.error(`[MCP] Bind failed on port ${port}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
const range = this.fixedPort
|
||||
? String(this.port)
|
||||
: `${this.basePort}-${this.maxPort}`;
|
||||
const hint = this.fixedPort
|
||||
? "Try removing GODOT_MCP_PORT from your client config to enable auto-scanning, or kill the process holding the port."
|
||||
: "All ports are occupied — likely too many parallel Claude Code sessions or stale node MCP processes.";
|
||||
throw new GodotConnectionError(`Failed to bind WebSocket server on port range ${range}. ` +
|
||||
`Last error: ${lastError?.message ?? "unknown"}. ${hint}`);
|
||||
}
|
||||
/** Try to bind a single WebSocketServer. Resolves once 'listening' fires, rejects on bind error. */
|
||||
bindWebSocketServer(port) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const wss = new WebSocketServer({ port, host: "127.0.0.1" });
|
||||
const onError = (err) => {
|
||||
wss.off("listening", onListening);
|
||||
wss.close();
|
||||
reject(err);
|
||||
};
|
||||
const onListening = () => {
|
||||
wss.off("error", onError);
|
||||
// Re-attach a runtime error handler now that the server is live.
|
||||
// Pre-bind errors fail the connect attempt; post-bind errors are logged.
|
||||
wss.on("error", (err) => {
|
||||
console.error("[MCP] WebSocket server error:", err.message);
|
||||
});
|
||||
resolve(wss);
|
||||
};
|
||||
wss.once("error", onError);
|
||||
wss.once("listening", onListening);
|
||||
});
|
||||
}
|
||||
attachConnectionHandler(wss) {
|
||||
wss.on("connection", (ws) => {
|
||||
console.error("[MCP] Godot editor connected");
|
||||
// Enable OS-level TCP keepalive so half-open sockets surface faster
|
||||
// than the Windows default (~2 hours). Application-level heartbeat
|
||||
// below is still the primary detection mechanism.
|
||||
const sock = ws._socket;
|
||||
sock?.setKeepAlive?.(true, TCP_KEEPALIVE_DELAY_MS);
|
||||
if (this.client) {
|
||||
this.client.close(1000, "Replaced by new connection");
|
||||
}
|
||||
this.client = ws;
|
||||
this.lastPongAt = Date.now();
|
||||
this.startHeartbeat();
|
||||
ws.on("message", (data) => {
|
||||
this.handleMessage(data.toString());
|
||||
});
|
||||
ws.on("close", () => {
|
||||
console.error("[MCP] Godot editor disconnected");
|
||||
if (this.client === ws) {
|
||||
this.client = null;
|
||||
this.stopHeartbeat();
|
||||
this.rejectAllPending(new GodotConnectionError("Godot disconnected"));
|
||||
}
|
||||
});
|
||||
ws.on("error", (err) => {
|
||||
console.error("[MCP] WebSocket error:", err.message);
|
||||
});
|
||||
});
|
||||
}
|
||||
disconnect() {
|
||||
this.stopHeartbeat();
|
||||
if (this.client) {
|
||||
this.client.close(1000, "Server shutting down");
|
||||
this.client = null;
|
||||
}
|
||||
if (this.wss) {
|
||||
this.wss.close();
|
||||
this.wss = null;
|
||||
}
|
||||
this.rejectAllPending(new GodotConnectionError("Server shut down"));
|
||||
}
|
||||
isConnected() {
|
||||
return this.client?.readyState === WebSocket.OPEN;
|
||||
}
|
||||
getPort() {
|
||||
return this.port;
|
||||
}
|
||||
async sendCommand(method, params = {}) {
|
||||
if (!this.isConnected()) {
|
||||
throw new GodotConnectionError("Godot editor is not connected. Make sure the Godot MCP Pro plugin is enabled and the editor is running.");
|
||||
}
|
||||
const id = randomUUID();
|
||||
const request = {
|
||||
jsonrpc: "2.0",
|
||||
method,
|
||||
params,
|
||||
id,
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
this.pendingRequests.delete(id);
|
||||
reject(new TimeoutError(method, COMMAND_TIMEOUT_MS));
|
||||
}, COMMAND_TIMEOUT_MS);
|
||||
this.pendingRequests.set(id, {
|
||||
resolve: resolve,
|
||||
reject,
|
||||
timer,
|
||||
});
|
||||
this.client.send(JSON.stringify(request));
|
||||
});
|
||||
}
|
||||
handleMessage(data) {
|
||||
let msg;
|
||||
try {
|
||||
msg = JSON.parse(data);
|
||||
}
|
||||
catch {
|
||||
console.error("[MCP] Failed to parse message from Godot:", data);
|
||||
return;
|
||||
}
|
||||
const method = msg.method;
|
||||
if (method === "pong") {
|
||||
this.lastPongAt = Date.now();
|
||||
return;
|
||||
}
|
||||
// Godot may also send unsolicited pings — reply so its inactivity timer resets
|
||||
if (method === "ping") {
|
||||
this.lastPongAt = Date.now();
|
||||
if (this.isConnected()) {
|
||||
this.client.send(JSON.stringify({ jsonrpc: "2.0", method: "pong", params: {} }));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!msg.id)
|
||||
return;
|
||||
const pending = this.pendingRequests.get(msg.id);
|
||||
if (!pending)
|
||||
return;
|
||||
clearTimeout(pending.timer);
|
||||
this.pendingRequests.delete(msg.id);
|
||||
if (msg.error) {
|
||||
pending.reject(new GodotCommandError(msg.error.code, msg.error.message, msg.error.data));
|
||||
}
|
||||
else {
|
||||
pending.resolve(msg.result);
|
||||
}
|
||||
}
|
||||
rejectAllPending(error) {
|
||||
for (const [, pending] of this.pendingRequests) {
|
||||
clearTimeout(pending.timer);
|
||||
pending.reject(error);
|
||||
}
|
||||
this.pendingRequests.clear();
|
||||
}
|
||||
startHeartbeat() {
|
||||
this.stopHeartbeat();
|
||||
this.heartbeatTimer = setInterval(() => {
|
||||
if (!this.isConnected())
|
||||
return;
|
||||
// If Godot has been silent for too long, the socket is likely half-open.
|
||||
// terminate() forcibly destroys the TCP socket (vs close() which waits
|
||||
// for a FIN ack that will never arrive on a dead link).
|
||||
if (Date.now() - this.lastPongAt > HEARTBEAT_TIMEOUT_MS) {
|
||||
console.error(`[MCP] Heartbeat timeout (no pong for ${HEARTBEAT_TIMEOUT_MS}ms) — terminating dead connection`);
|
||||
const dead = this.client;
|
||||
this.client = null;
|
||||
this.stopHeartbeat();
|
||||
this.rejectAllPending(new GodotConnectionError("Heartbeat timeout — Godot connection lost"));
|
||||
dead?.terminate();
|
||||
return;
|
||||
}
|
||||
this.client.send(JSON.stringify({ jsonrpc: "2.0", method: "ping", params: {} }));
|
||||
}, HEARTBEAT_INTERVAL_MS);
|
||||
}
|
||||
stopHeartbeat() {
|
||||
if (this.heartbeatTimer) {
|
||||
clearInterval(this.heartbeatTimer);
|
||||
this.heartbeatTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=godot-connection.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
export {};
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
||||
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env node
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
||||
import { createServer } from "node:http";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { GodotConnection } from "./godot-connection.js";
|
||||
import { registerProjectTools } from "./tools/project-tools.js";
|
||||
import { registerSceneTools } from "./tools/scene-tools.js";
|
||||
import { registerNodeTools } from "./tools/node-tools.js";
|
||||
import { registerScriptTools } from "./tools/script-tools.js";
|
||||
import { registerEditorTools } from "./tools/editor-tools.js";
|
||||
import { registerInputTools } from "./tools/input-tools.js";
|
||||
import { registerRuntimeTools } from "./tools/runtime-tools.js";
|
||||
import { registerAnimationTools } from "./tools/animation-tools.js";
|
||||
import { registerTilemapTools } from "./tools/tilemap-tools.js";
|
||||
import { registerThemeTools } from "./tools/theme-tools.js";
|
||||
import { registerProfilingTools } from "./tools/profiling-tools.js";
|
||||
import { registerBatchTools } from "./tools/batch-tools.js";
|
||||
import { registerShaderTools } from "./tools/shader-tools.js";
|
||||
import { registerExportTools } from "./tools/export-tools.js";
|
||||
import { registerResourceTools } from "./tools/resource-tools.js";
|
||||
import { registerAnimationTreeTools } from "./tools/animation-tree-tools.js";
|
||||
import { registerPhysicsTools } from "./tools/physics-tools.js";
|
||||
import { registerScene3DTools } from "./tools/scene-3d-tools.js";
|
||||
import { registerParticleTools } from "./tools/particle-tools.js";
|
||||
import { registerNavigationTools } from "./tools/navigation-tools.js";
|
||||
import { registerAudioTools } from "./tools/audio-tools.js";
|
||||
import { registerTestTools } from "./tools/test-tools.js";
|
||||
import { registerAnalysisTools } from "./tools/analysis-tools.js";
|
||||
import { registerInputMapTools } from "./tools/input-map-tools.js";
|
||||
import { registerAndroidTools } from "./tools/android-tools.js";
|
||||
import { MINIMAL_TOOLS, createFilteredServer } from "./utils/tool-filter.js";
|
||||
import { loadInstructions } from "./utils/load-instructions.js";
|
||||
const MINIMAL_MODE = process.argv.includes("--minimal");
|
||||
const THREED_MODE = process.argv.includes("--3d");
|
||||
const LITE_MODE = process.argv.includes("--lite") || MINIMAL_MODE || THREED_MODE;
|
||||
const HTTP_MODE = process.argv.includes("--http");
|
||||
const HTTP_PORT = parseInt(process.argv.find((_, i, a) => a[i - 1] === "--http-port") ||
|
||||
process.env.GODOT_MCP_HTTP_PORT ||
|
||||
"8001");
|
||||
const explicitPort = process.env.GODOT_MCP_PORT;
|
||||
const godot = new GodotConnection(parseInt(explicitPort || "6505"), !!explicitPort);
|
||||
const serverName = MINIMAL_MODE
|
||||
? "godot-mcp-pro-minimal"
|
||||
: THREED_MODE
|
||||
? "godot-mcp-pro-3d"
|
||||
: LITE_MODE
|
||||
? "godot-mcp-pro-lite"
|
||||
: "godot-mcp-pro";
|
||||
const server = new McpServer({
|
||||
name: serverName,
|
||||
version: "1.15.1",
|
||||
}, {
|
||||
instructions: loadInstructions(),
|
||||
});
|
||||
// In minimal mode, wrap the server to filter tool registrations
|
||||
const toolServer = MINIMAL_MODE ? createFilteredServer(server, MINIMAL_TOOLS) : server;
|
||||
// Core tools (always registered)
|
||||
registerProjectTools(toolServer, godot);
|
||||
registerSceneTools(toolServer, godot);
|
||||
registerNodeTools(toolServer, godot);
|
||||
registerScriptTools(toolServer, godot);
|
||||
registerEditorTools(toolServer, godot);
|
||||
registerInputTools(toolServer, godot);
|
||||
registerRuntimeTools(toolServer, godot);
|
||||
registerInputMapTools(toolServer, godot);
|
||||
// 3D-critical tools (registered in FULL and --3d modes)
|
||||
// Core (81) + Physics (6) + AnimationTree (8) + Navigation (5) = exactly 100 tools
|
||||
if (!LITE_MODE || THREED_MODE) {
|
||||
registerPhysicsTools(server, godot);
|
||||
registerAnimationTreeTools(server, godot);
|
||||
registerNavigationTools(server, godot);
|
||||
}
|
||||
// Extended tools (Full mode only)
|
||||
if (!LITE_MODE) {
|
||||
registerAnimationTools(server, godot);
|
||||
registerAudioTools(server, godot);
|
||||
registerBatchTools(server, godot);
|
||||
registerExportTools(server, godot);
|
||||
registerParticleTools(server, godot);
|
||||
registerProfilingTools(server, godot);
|
||||
registerResourceTools(server, godot);
|
||||
registerScene3DTools(server, godot);
|
||||
registerShaderTools(server, godot);
|
||||
registerTestTools(server, godot);
|
||||
registerThemeTools(server, godot);
|
||||
registerTilemapTools(server, godot);
|
||||
registerAnalysisTools(server, godot);
|
||||
registerAndroidTools(server, godot);
|
||||
}
|
||||
// Start server
|
||||
async function main() {
|
||||
// Attempt initial connection to Godot (non-blocking).
|
||||
// If this fails (all ports occupied, etc.), tool calls will fail with a
|
||||
// clear error message from sendCommand until the user restarts the server.
|
||||
godot.connect().catch((err) => {
|
||||
console.error(`[MCP] Failed to start WebSocket server: ${err.message}`);
|
||||
});
|
||||
if (HTTP_MODE) {
|
||||
// Streamable HTTP transport — clients connect via http://host:port/mcp
|
||||
const transport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: () => randomUUID(),
|
||||
});
|
||||
await server.connect(transport);
|
||||
const httpServer = createServer(async (req, res) => {
|
||||
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
||||
if (url.pathname === "/mcp") {
|
||||
await transport.handleRequest(req, res);
|
||||
}
|
||||
else {
|
||||
res.writeHead(404).end("Not Found");
|
||||
}
|
||||
});
|
||||
httpServer.listen(HTTP_PORT, () => {
|
||||
const mode = MINIMAL_MODE ? "MINIMAL " : THREED_MODE ? "3D " : LITE_MODE ? "LITE " : "";
|
||||
console.error(`[MCP] Godot MCP Pro ${mode}started (HTTP transport on http://127.0.0.1:${HTTP_PORT}/mcp)`);
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Default stdio transport
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
const modeLabel = MINIMAL_MODE
|
||||
? "[MCP] Godot MCP Pro MINIMAL started (35 tools, stdio transport)"
|
||||
: THREED_MODE
|
||||
? "[MCP] Godot MCP Pro 3D started (103 tools, stdio transport)"
|
||||
: LITE_MODE
|
||||
? "[MCP] Godot MCP Pro LITE started (84 tools, stdio transport)"
|
||||
: "[MCP] Godot MCP Pro started (stdio transport)";
|
||||
console.error(modeLabel);
|
||||
}
|
||||
}
|
||||
main().catch((err) => {
|
||||
console.error("[MCP] Fatal error:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
//# sourceMappingURL=index.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* godot-mcp-setup — Setup and management CLI for Godot MCP Pro
|
||||
*
|
||||
* Commands:
|
||||
* install Install dependencies and build the server
|
||||
* check-update Check if a newer version is available on GitHub
|
||||
* configure Auto-detect AI client and generate MCP config
|
||||
* doctor Diagnose environment (Node.js, npm, build status)
|
||||
*/
|
||||
export {};
|
||||
//# sourceMappingURL=setup.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":";AAEA;;;;;;;;GAQG"}
|
||||
@@ -0,0 +1,273 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* godot-mcp-setup — Setup and management CLI for Godot MCP Pro
|
||||
*
|
||||
* Commands:
|
||||
* install Install dependencies and build the server
|
||||
* check-update Check if a newer version is available on GitHub
|
||||
* configure Auto-detect AI client and generate MCP config
|
||||
* doctor Diagnose environment (Node.js, npm, build status)
|
||||
*/
|
||||
import { execSync } from "child_process";
|
||||
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
||||
import { resolve, dirname, join } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
// Server root is one level up from build/
|
||||
const SERVER_DIR = resolve(__dirname, "..");
|
||||
const PACKAGE_JSON = join(SERVER_DIR, "package.json");
|
||||
const BUILD_INDEX = join(SERVER_DIR, "build", "index.js");
|
||||
const GITHUB_REPO = "youichi-uda/godot-mcp-pro";
|
||||
// ─── Utilities ────────────────────────────────────────────────
|
||||
function getVersion() {
|
||||
try {
|
||||
const pkg = JSON.parse(readFileSync(PACKAGE_JSON, "utf-8"));
|
||||
return pkg.version || "unknown";
|
||||
}
|
||||
catch {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
function run(cmd, cwd) {
|
||||
try {
|
||||
return execSync(cmd, {
|
||||
cwd: cwd || SERVER_DIR,
|
||||
encoding: "utf-8",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
}).trim();
|
||||
}
|
||||
catch (err) {
|
||||
return err.stderr?.trim() || err.message || "command failed";
|
||||
}
|
||||
}
|
||||
function check(label, ok, detail) {
|
||||
const icon = ok ? "✓" : "✗";
|
||||
const line = detail ? `${label}: ${detail}` : label;
|
||||
console.log(` ${icon} ${line}`);
|
||||
}
|
||||
/** Compare semver strings. Returns >0 if a > b, <0 if a < b, 0 if equal. */
|
||||
function compareSemver(a, b) {
|
||||
const pa = a.split(".").map(Number);
|
||||
const pb = b.split(".").map(Number);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const diff = (pa[i] || 0) - (pb[i] || 0);
|
||||
if (diff !== 0)
|
||||
return diff;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// ─── Commands ─────────────────────────────────────────────────
|
||||
async function cmdInstall() {
|
||||
console.log("Installing Godot MCP Pro server...\n");
|
||||
console.log("[1/2] Installing dependencies...");
|
||||
try {
|
||||
execSync("npm install", { cwd: SERVER_DIR, stdio: "inherit" });
|
||||
}
|
||||
catch {
|
||||
console.error("\nFailed to install dependencies. Make sure npm is available.");
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("\n[2/2] Building server...");
|
||||
try {
|
||||
execSync("npm run build", { cwd: SERVER_DIR, stdio: "inherit" });
|
||||
}
|
||||
catch {
|
||||
console.error("\nBuild failed. Check for TypeScript errors above.");
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`\nDone! Server built at: ${BUILD_INDEX}`);
|
||||
console.log(`Version: ${getVersion()}`);
|
||||
console.log("\nNext step: Run 'node build/setup.js configure' to set up your AI client.");
|
||||
}
|
||||
async function cmdCheckUpdate() {
|
||||
const current = getVersion();
|
||||
console.log(`Current version: ${current}\n`);
|
||||
console.log(`Checking GitHub releases for ${GITHUB_REPO}...`);
|
||||
try {
|
||||
const res = await fetch(`https://api.github.com/repos/${GITHUB_REPO}/releases/latest`, { headers: { "User-Agent": "godot-mcp-pro-setup" } });
|
||||
if (!res.ok) {
|
||||
if (res.status === 404) {
|
||||
console.log("No releases found on GitHub.");
|
||||
return;
|
||||
}
|
||||
console.error(`GitHub API error: ${res.status} ${res.statusText}`);
|
||||
return;
|
||||
}
|
||||
const data = (await res.json());
|
||||
const latest = data.tag_name.replace(/^v/, "");
|
||||
if (compareSemver(latest, current) > 0) {
|
||||
console.log(`\nUpdate available: v${latest} (current: v${current})`);
|
||||
console.log(`Download: ${data.html_url}`);
|
||||
console.log("\nTo update: download the new version, replace server/src/, and run 'node build/setup.js install'");
|
||||
}
|
||||
else {
|
||||
console.log(`\nUp to date! (${current})`);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`Failed to check for updates: ${err.message}`);
|
||||
}
|
||||
}
|
||||
async function cmdConfigure() {
|
||||
const serverPath = resolve(BUILD_INDEX).replace(/\\/g, "/");
|
||||
if (!existsSync(BUILD_INDEX)) {
|
||||
console.error("Server not built yet. Run 'node build/setup.js install' first.");
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("Detecting AI clients...\n");
|
||||
// Detect available clients by checking config file locations
|
||||
const home = process.env.HOME || process.env.USERPROFILE || "";
|
||||
const cwd = process.cwd();
|
||||
const candidates = [
|
||||
{
|
||||
name: "Claude Code (project)",
|
||||
configPath: join(cwd, ".mcp.json"),
|
||||
configKey: "godot-mcp-pro",
|
||||
},
|
||||
{
|
||||
name: "Cursor (project)",
|
||||
configPath: join(cwd, ".cursor", "mcp.json"),
|
||||
configKey: "godot-mcp-pro",
|
||||
},
|
||||
{
|
||||
name: "Windsurf (project)",
|
||||
configPath: join(cwd, ".windsurf", "mcp.json"),
|
||||
configKey: "godot-mcp-pro",
|
||||
},
|
||||
{
|
||||
name: "Claude Desktop",
|
||||
configPath: join(home, process.platform === "win32"
|
||||
? "AppData/Roaming/Claude/claude_desktop_config.json"
|
||||
: process.platform === "darwin"
|
||||
? "Library/Application Support/Claude/claude_desktop_config.json"
|
||||
: ".config/claude/claude_desktop_config.json"),
|
||||
configKey: "godot-mcp-pro",
|
||||
},
|
||||
];
|
||||
// Find existing configs
|
||||
const existing = candidates.filter((c) => existsSync(c.configPath));
|
||||
const missing = candidates.filter((c) => !existsSync(c.configPath));
|
||||
if (existing.length > 0) {
|
||||
console.log("Found existing configs:");
|
||||
for (const c of existing) {
|
||||
console.log(` ✓ ${c.name}: ${c.configPath}`);
|
||||
}
|
||||
}
|
||||
// Default: create .mcp.json in cwd (Claude Code)
|
||||
const target = candidates[0]; // Claude Code project-level
|
||||
// No GODOT_MCP_PORT env: lets the server auto-scan 6505-6509 so multiple
|
||||
// Claude Code sessions can each grab a free port. Pinning a single port
|
||||
// here would force every session to collide on 6505.
|
||||
const entry = {
|
||||
command: "node",
|
||||
args: [serverPath],
|
||||
};
|
||||
let config;
|
||||
if (existsSync(target.configPath)) {
|
||||
try {
|
||||
config = JSON.parse(readFileSync(target.configPath, "utf-8"));
|
||||
if (!config.mcpServers)
|
||||
config.mcpServers = {};
|
||||
}
|
||||
catch {
|
||||
config = { mcpServers: {} };
|
||||
}
|
||||
}
|
||||
else {
|
||||
config = { mcpServers: {} };
|
||||
}
|
||||
if (config.mcpServers[target.configKey]) {
|
||||
console.log(`\n${target.name} already configured in ${target.configPath}`);
|
||||
console.log("Updating server path...");
|
||||
}
|
||||
config.mcpServers[target.configKey] = entry;
|
||||
const dir = dirname(target.configPath);
|
||||
if (!existsSync(dir))
|
||||
mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(target.configPath, JSON.stringify(config, null, 2) + "\n");
|
||||
console.log(`\nWrote config to: ${target.configPath}`);
|
||||
console.log(`Server path: ${serverPath}`);
|
||||
console.log("\nYou're all set! Start your AI assistant to begin using Godot MCP Pro.");
|
||||
}
|
||||
function cmdDoctor() {
|
||||
console.log("Godot MCP Pro — Environment Check\n");
|
||||
// Node.js
|
||||
const nodeVer = run("node --version");
|
||||
const nodeOk = nodeVer.startsWith("v") && parseInt(nodeVer.slice(1)) >= 18;
|
||||
check("Node.js", nodeOk, nodeVer);
|
||||
// npm
|
||||
const npmVer = run("npm --version");
|
||||
const npmOk = !npmVer.includes("not found") && !npmVer.includes("failed");
|
||||
check("npm", npmOk, npmVer);
|
||||
// Dependencies installed
|
||||
const nodeModules = existsSync(join(SERVER_DIR, "node_modules"));
|
||||
check("Dependencies installed", nodeModules);
|
||||
// Server built
|
||||
const built = existsSync(BUILD_INDEX);
|
||||
check("Server built", built, built ? BUILD_INDEX : "run 'node build/setup.js install'");
|
||||
// Version
|
||||
console.log(`\n Version: ${getVersion()}`);
|
||||
// Overall
|
||||
const allOk = nodeOk && npmOk && nodeModules && built;
|
||||
console.log(allOk ? "\nAll good!" : "\nSome issues found. Fix them above.");
|
||||
if (!allOk)
|
||||
process.exit(1);
|
||||
}
|
||||
// ─── Main ─────────────────────────────────────────────────────
|
||||
function showHelp() {
|
||||
console.log(`godot-mcp-setup — Setup and management for Godot MCP Pro
|
||||
|
||||
Usage: node build/setup.js <command>
|
||||
|
||||
Commands:
|
||||
install Install dependencies and build the server
|
||||
check-update Check if a newer version is available on GitHub
|
||||
configure Auto-detect AI client and generate .mcp.json config
|
||||
doctor Check Node.js, npm, and build status
|
||||
|
||||
Options:
|
||||
--help Show this help
|
||||
--version Show current version
|
||||
|
||||
Examples:
|
||||
node build/setup.js install
|
||||
node build/setup.js doctor
|
||||
node build/setup.js configure
|
||||
node build/setup.js check-update`);
|
||||
}
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const cmd = args[0];
|
||||
if (!cmd || cmd === "--help" || cmd === "-h") {
|
||||
showHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
if (cmd === "--version" || cmd === "-v") {
|
||||
console.log(getVersion());
|
||||
process.exit(0);
|
||||
}
|
||||
switch (cmd) {
|
||||
case "install":
|
||||
await cmdInstall();
|
||||
break;
|
||||
case "check-update":
|
||||
await cmdCheckUpdate();
|
||||
break;
|
||||
case "configure":
|
||||
await cmdConfigure();
|
||||
break;
|
||||
case "doctor":
|
||||
cmdDoctor();
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown command: ${cmd}`);
|
||||
showHelp();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
main().catch((err) => {
|
||||
console.error("Fatal:", err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
//# sourceMappingURL=setup.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { GodotConnection } from "../godot-connection.js";
|
||||
export declare function registerAnalysisTools(server: McpServer, godot: GodotConnection): void;
|
||||
//# sourceMappingURL=analysis-tools.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"analysis-tools.d.ts","sourceRoot":"","sources":["../../src/tools/analysis-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,eAAe,GACrB,IAAI,CAmGN"}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { z } from "zod";
|
||||
import { formatErrorForMcp } from "../utils/errors.js";
|
||||
export function registerAnalysisTools(server, godot) {
|
||||
server.tool("find_unused_resources", "Scan the project for resource files (.tres, .tscn, .png, .wav, .ogg, .ttf, .gdshader, etc.) that are not referenced by any .tscn, .gd, or .tres file. Useful for cleaning up unused assets.", {
|
||||
path: z.string().optional().describe("Root path to scan (default: res://)"),
|
||||
include_addons: z.boolean().optional().describe("Include addons/ directory in scan (default: false)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("find_unused_resources", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("analyze_signal_flow", "Map all signal connections in the currently edited scene. Returns a graph-like structure showing which nodes emit which signals and which nodes receive them.", {}, async () => {
|
||||
try {
|
||||
const result = await godot.sendCommand("analyze_signal_flow");
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("analyze_scene_complexity", "Analyze a scene's complexity: total node count, max nesting depth, nodes grouped by type, attached scripts, and potential issues (too many nodes, deep nesting).", {
|
||||
path: z.string().optional().describe("Scene path to analyze (default: currently edited scene)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("analyze_scene_complexity", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("find_script_references", "Find all places where a given script path, class_name, or resource path is referenced across the project. Searches .tscn, .gd, and .tres files.", {
|
||||
query: z.string().describe("The script path, class_name, or resource path to search for (e.g. 'res://scripts/player.gd', 'PlayerController', 'res://assets/icon.png')"),
|
||||
path: z.string().optional().describe("Root path to search (default: res://)"),
|
||||
include_addons: z.boolean().optional().describe("Include addons/ directory in search (default: false)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("find_script_references", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("detect_circular_dependencies", "Check for circular scene dependencies where Scene A instances Scene B which instances Scene A (directly or indirectly). Walks all .tscn files and builds a dependency graph.", {
|
||||
path: z.string().optional().describe("Root path to scan (default: res://)"),
|
||||
include_addons: z.boolean().optional().describe("Include addons/ directory in scan (default: false)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("detect_circular_dependencies", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_project_statistics", "Get overall project statistics: file counts by extension, total script lines, scene count, resource count, autoload list, and enabled plugins.", {
|
||||
path: z.string().optional().describe("Root path to scan (default: res://)"),
|
||||
include_addons: z.boolean().optional().describe("Include addons/ directory in statistics (default: false)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_project_statistics", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=analysis-tools.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"analysis-tools.js","sourceRoot":"","sources":["../../src/tools/analysis-tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,UAAU,qBAAqB,CACnC,MAAiB,EACjB,KAAsB;IAEtB,MAAM,CAAC,IAAI,CACT,uBAAuB,EACvB,6LAA6L,EAC7L;QACE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;QAC3E,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oDAAoD,CAAC;KACtG,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAC;YACxE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,qBAAqB,EACrB,+JAA+J,EAC/J,EAAE,EACF,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,qBAAqB,CAAC,CAAC;YAC9D,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,0BAA0B,EAC1B,kKAAkK,EAClK;QACE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,yDAAyD,CAAC;KAChG,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,0BAA0B,EAAE,MAAM,CAAC,CAAC;YAC3E,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,wBAAwB,EACxB,iJAAiJ,EACjJ;QACE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,2IAA2I,CAAC;QACvK,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;QAC7E,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sDAAsD,CAAC;KACxG,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,wBAAwB,EAAE,MAAM,CAAC,CAAC;YACzE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,8BAA8B,EAC9B,8KAA8K,EAC9K;QACE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;QAC3E,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oDAAoD,CAAC;KACtG,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,8BAA8B,EAAE,MAAM,CAAC,CAAC;YAC/E,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,wBAAwB,EACxB,gJAAgJ,EAChJ;QACE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;QAC3E,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0DAA0D,CAAC;KAC5G,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,wBAAwB,EAAE,MAAM,CAAC,CAAC;YACzE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { GodotConnection } from "../godot-connection.js";
|
||||
export declare function registerAndroidTools(server: McpServer, godot: GodotConnection): void;
|
||||
//# sourceMappingURL=android-tools.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"android-tools.d.ts","sourceRoot":"","sources":["../../src/tools/android-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,eAAe,GACrB,IAAI,CAoDN"}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { z } from "zod";
|
||||
import { formatErrorForMcp } from "../utils/errors.js";
|
||||
export function registerAndroidTools(server, godot) {
|
||||
server.tool("list_android_devices", "List Android devices visible to adb (parses 'adb devices -l'). Uses the path configured in Editor Settings > Export > Android > Adb, falls back to 'adb' on PATH.", {}, async () => {
|
||||
try {
|
||||
const result = await godot.sendCommand("list_android_devices");
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_android_preset_info", "Read metadata (package name, export path, runnable flag) from an Android export preset in export_presets.cfg. If no preset is specified, returns the first Android preset.", {
|
||||
preset_name: z.string().optional().describe("Preset name as shown in Project > Export"),
|
||||
preset_index: z.number().optional().describe("Preset index (alternative to name)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_android_preset_info", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("deploy_to_android", "Export APK via Godot CLI, install it on a connected Android device via adb, and optionally launch the main activity. Equivalent to Godot's Remote Deploy button. Requires a configured Android export preset and adb on PATH (or set in Editor Settings). This call is synchronous and may take tens of seconds to complete.", {
|
||||
preset_name: z.string().optional().describe("Android export preset name (defaults to first Android preset)"),
|
||||
preset_index: z.number().optional().describe("Preset index (alternative to name)"),
|
||||
device_serial: z.string().optional().describe("adb device serial (omit to use default device)"),
|
||||
debug: z.boolean().optional().describe("Debug export (default: true)"),
|
||||
launch: z.boolean().optional().describe("Launch the app after install (default: true)"),
|
||||
skip_export: z.boolean().optional().describe("Skip the export step and install the existing APK at the preset's export_path (default: false)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("deploy_to_android", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=android-tools.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"android-tools.js","sourceRoot":"","sources":["../../src/tools/android-tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,UAAU,oBAAoB,CAClC,MAAiB,EACjB,KAAsB;IAEtB,MAAM,CAAC,IAAI,CACT,sBAAsB,EACtB,mKAAmK,EACnK,EAAE,EACF,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC;YAC/D,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,yBAAyB,EACzB,4KAA4K,EAC5K;QACE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0CAA0C,CAAC;QACvF,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oCAAoC,CAAC;KACnF,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,yBAAyB,EAAE,MAAM,CAAC,CAAC;YAC1E,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,mBAAmB,EACnB,8TAA8T,EAC9T;QACE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+DAA+D,CAAC;QAC5G,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oCAAoC,CAAC;QAClF,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gDAAgD,CAAC;QAC/F,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,8BAA8B,CAAC;QACtE,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,8CAA8C,CAAC;QACvF,WAAW,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gGAAgG,CAAC;KAC/I,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;YACpE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { GodotConnection } from "../godot-connection.js";
|
||||
export declare function registerAnimationTools(server: McpServer, godot: GodotConnection): void;
|
||||
//# sourceMappingURL=animation-tools.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"animation-tools.d.ts","sourceRoot":"","sources":["../../src/tools/animation-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,eAAe,GACrB,IAAI,CA8GN"}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { z } from "zod";
|
||||
import { formatErrorForMcp } from "../utils/errors.js";
|
||||
export function registerAnimationTools(server, godot) {
|
||||
server.tool("list_animations", "List all animations in an AnimationPlayer node", {
|
||||
node_path: z.string().describe("Path to the AnimationPlayer node"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("list_animations", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("create_animation", "Create a new animation in an AnimationPlayer", {
|
||||
node_path: z.string().describe("Path to the AnimationPlayer node"),
|
||||
name: z.string().describe("Name for the new animation"),
|
||||
length: z.number().optional().describe("Animation length in seconds (default: 1.0)"),
|
||||
loop_mode: z.number().optional().describe("Loop mode: 0=none, 1=linear, 2=pingpong (default: 0)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("create_animation", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("add_animation_track", "Add a track to an animation (value, position, rotation, scale, method, bezier)", {
|
||||
node_path: z.string().describe("Path to the AnimationPlayer node"),
|
||||
animation: z.string().describe("Animation name"),
|
||||
track_path: z.string().describe("Node path and property for the track (e.g. 'Sprite2D:position')"),
|
||||
track_type: z.string().optional().describe("Track type: value, position_2d, rotation_2d, scale_2d, method, bezier, blend_shape (default: value)"),
|
||||
update_mode: z.string().optional().describe("Update mode for value tracks: continuous, discrete, capture"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("add_animation_track", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("set_animation_keyframe", "Insert a keyframe into an animation track", {
|
||||
node_path: z.string().describe("Path to the AnimationPlayer node"),
|
||||
animation: z.string().describe("Animation name"),
|
||||
track_index: z.number().describe("Track index"),
|
||||
time: z.number().describe("Time position in seconds"),
|
||||
value: z.union([z.string(), z.number(), z.boolean()]).describe("Keyframe value. Strings auto-parsed for Vector2, Color, etc."),
|
||||
easing: z.number().optional().describe("Easing/transition value. 1.0=linear, <1.0=ease-in, >1.0=ease-out. Use negative for in-out variants. (default: 1.0)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("set_animation_keyframe", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_animation_info", "Get detailed info about an animation including all tracks and keyframes", {
|
||||
node_path: z.string().describe("Path to the AnimationPlayer node"),
|
||||
animation: z.string().describe("Animation name"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_animation_info", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("remove_animation", "Remove an animation from an AnimationPlayer", {
|
||||
node_path: z.string().describe("Path to the AnimationPlayer node"),
|
||||
name: z.string().describe("Name of the animation to remove"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("remove_animation", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=animation-tools.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"animation-tools.js","sourceRoot":"","sources":["../../src/tools/animation-tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,UAAU,sBAAsB,CACpC,MAAiB,EACjB,KAAsB;IAEtB,MAAM,CAAC,IAAI,CACT,iBAAiB,EACjB,gDAAgD,EAChD;QACE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;KACnE,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;YAClE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,kBAAkB,EAClB,8CAA8C,EAC9C;QACE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;QAClE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,4BAA4B,CAAC;QACvD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,4CAA4C,CAAC;QACpF,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sDAAsD,CAAC;KAClG,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;YACnE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,qBAAqB,EACrB,gFAAgF,EAChF;QACE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;QAClE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QAChD,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,iEAAiE,CAAC;QAClG,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qGAAqG,CAAC;QACjJ,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6DAA6D,CAAC;KAC3G,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;YACtE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,wBAAwB,EACxB,2CAA2C,EAC3C;QACE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;QAClE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QAChD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC;QAC/C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,0BAA0B,CAAC;QACrD,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,8DAA8D,CAAC;QAC9H,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oHAAoH,CAAC;KAC7J,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,wBAAwB,EAAE,MAAM,CAAC,CAAC;YACzE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,oBAAoB,EACpB,yEAAyE,EACzE;QACE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;QAClE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;KACjD,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC;YACrE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,kBAAkB,EAClB,6CAA6C,EAC7C;QACE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;QAClE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;KAC7D,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;YACnE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { GodotConnection } from "../godot-connection.js";
|
||||
export declare function registerAnimationTreeTools(server: McpServer, godot: GodotConnection): void;
|
||||
//# sourceMappingURL=animation-tree-tools.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"animation-tree-tools.d.ts","sourceRoot":"","sources":["../../src/tools/animation-tree-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,eAAe,GACrB,IAAI,CA+JN"}
|
||||
@@ -0,0 +1,124 @@
|
||||
import { z } from "zod";
|
||||
import { formatErrorForMcp } from "../utils/errors.js";
|
||||
export function registerAnimationTreeTools(server, godot) {
|
||||
server.tool("create_animation_tree", "Create an AnimationTree node with an AnimationNodeStateMachine as root, optionally linked to an AnimationPlayer", {
|
||||
node_path: z.string().describe("Path to the parent node where the AnimationTree will be added"),
|
||||
anim_player: z.string().optional().describe("Relative path from the AnimationTree to the AnimationPlayer (e.g. '../AnimationPlayer')"),
|
||||
name: z.string().optional().describe("Name for the AnimationTree node (default: 'AnimationTree')"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("create_animation_tree", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_animation_tree_structure", "Read the full structure of an AnimationTree including all states, transitions, and blend tree nodes", {
|
||||
node_path: z.string().describe("Path to the AnimationTree node"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_animation_tree_structure", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("add_state_machine_state", "Add a state to an AnimationNodeStateMachine (animation clip, blend tree, or nested state machine)", {
|
||||
node_path: z.string().describe("Path to the AnimationTree node"),
|
||||
state_name: z.string().describe("Name for the new state"),
|
||||
state_type: z.enum(["animation", "blend_tree", "state_machine"]).optional().describe("Type of state: 'animation' (default), 'blend_tree', or 'state_machine'"),
|
||||
animation: z.string().optional().describe("Animation name to play (only for state_type='animation')"),
|
||||
state_machine_path: z.string().optional().describe("Slash-separated path to a nested state machine (e.g. 'Run/SubState'). Empty or omit for root."),
|
||||
position_x: z.number().optional().describe("X position in the graph editor (default: 0)"),
|
||||
position_y: z.number().optional().describe("Y position in the graph editor (default: 0)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("add_state_machine_state", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("remove_state_machine_state", "Remove a state from an AnimationNodeStateMachine (also removes connected transitions)", {
|
||||
node_path: z.string().describe("Path to the AnimationTree node"),
|
||||
state_name: z.string().describe("Name of the state to remove"),
|
||||
state_machine_path: z.string().optional().describe("Slash-separated path to a nested state machine. Empty or omit for root."),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("remove_state_machine_state", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("add_state_machine_transition", "Add a transition between two states in an AnimationNodeStateMachine with configurable switch mode, advance mode, and expression conditions", {
|
||||
node_path: z.string().describe("Path to the AnimationTree node"),
|
||||
from_state: z.string().describe("Source state name (use 'Start' for the entry point)"),
|
||||
to_state: z.string().describe("Destination state name (use 'End' for the exit point)"),
|
||||
switch_mode: z.enum(["at_end", "immediate", "sync"]).optional().describe("When to switch: 'at_end' (wait for animation), 'immediate' (default), 'sync'"),
|
||||
advance_mode: z.enum(["disabled", "enabled", "auto"]).optional().describe("How to advance: 'disabled', 'enabled' (default, uses travel), 'auto' (automatic)"),
|
||||
advance_expression: z.string().optional().describe("GDScript expression that triggers this transition (e.g. 'is_running')"),
|
||||
xfade_time: z.number().optional().describe("Cross-fade time in seconds"),
|
||||
state_machine_path: z.string().optional().describe("Slash-separated path to a nested state machine. Empty or omit for root."),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("add_state_machine_transition", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("remove_state_machine_transition", "Remove a transition between two states in an AnimationNodeStateMachine", {
|
||||
node_path: z.string().describe("Path to the AnimationTree node"),
|
||||
from_state: z.string().describe("Source state name"),
|
||||
to_state: z.string().describe("Destination state name"),
|
||||
state_machine_path: z.string().optional().describe("Slash-separated path to a nested state machine. Empty or omit for root."),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("remove_state_machine_transition", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("set_blend_tree_node", "Add or replace a node inside an AnimationNodeBlendTree state (Add2, Blend2, TimeScale, Animation, etc.) with optional connection", {
|
||||
node_path: z.string().describe("Path to the AnimationTree node"),
|
||||
blend_tree_state: z.string().describe("Name of the BlendTree state in the state machine"),
|
||||
bt_node_name: z.string().describe("Name for the node inside the BlendTree"),
|
||||
bt_node_type: z.enum(["Animation", "Add2", "Blend2", "Add3", "Blend3", "TimeScale", "TimeSeek", "Transition", "OneShot", "Sub2"]).describe("Type of BlendTree node to create"),
|
||||
animation: z.string().optional().describe("Animation name (only for bt_node_type='Animation')"),
|
||||
connect_to: z.string().optional().describe("Name of another BlendTree node to connect this node's output to"),
|
||||
connect_port: z.number().optional().describe("Input port index on the target node (default: 0)"),
|
||||
state_machine_path: z.string().optional().describe("Slash-separated path to a nested state machine. Empty or omit for root."),
|
||||
position_x: z.number().optional().describe("X position in the graph editor (default: 0)"),
|
||||
position_y: z.number().optional().describe("Y position in the graph editor (default: 0)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("set_blend_tree_node", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("set_tree_parameter", "Set an AnimationTree parameter value (conditions, blend amounts, time scale, etc.)", {
|
||||
node_path: z.string().describe("Path to the AnimationTree node"),
|
||||
parameter: z.string().describe("Parameter path (e.g. 'conditions/is_running', 'Blend2/blend_amount'). 'parameters/' prefix is auto-added if missing."),
|
||||
value: z.union([z.string(), z.number(), z.boolean()]).describe("Parameter value. Strings are auto-parsed for Vector2, Color, etc."),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("set_tree_parameter", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=animation-tree-tools.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { GodotConnection } from "../godot-connection.js";
|
||||
export declare function registerAudioTools(server: McpServer, godot: GodotConnection): void;
|
||||
//# sourceMappingURL=audio-tools.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"audio-tools.d.ts","sourceRoot":"","sources":["../../src/tools/audio-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,eAAe,GACrB,IAAI,CAsHN"}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { z } from "zod";
|
||||
import { formatErrorForMcp } from "../utils/errors.js";
|
||||
export function registerAudioTools(server, godot) {
|
||||
server.tool("get_audio_bus_layout", "Get the entire audio bus layout: all buses with volumes, effects, send targets, solo/mute states", {}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_audio_bus_layout", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("add_audio_bus", "Add a new audio bus with name, volume, send target, solo, and mute settings", {
|
||||
name: z.string().describe("Name for the new audio bus"),
|
||||
volume_db: z.number().optional().describe("Volume in dB (default: 0)"),
|
||||
send: z.string().optional().describe("Name of the bus to send output to (e.g. 'Master')"),
|
||||
solo: z.boolean().optional().describe("Solo this bus (default: false)"),
|
||||
mute: z.boolean().optional().describe("Mute this bus (default: false)"),
|
||||
at_position: z.number().optional().describe("Bus index position to insert at (-1 = end)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("add_audio_bus", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("set_audio_bus", "Modify an existing audio bus: volume, solo, mute, bypass_effects, send, or rename", {
|
||||
name: z.string().describe("Name of the audio bus to modify"),
|
||||
volume_db: z.number().optional().describe("Volume in dB"),
|
||||
solo: z.boolean().optional().describe("Solo state"),
|
||||
mute: z.boolean().optional().describe("Mute state"),
|
||||
bypass_effects: z.boolean().optional().describe("Bypass all effects on this bus"),
|
||||
send: z.string().optional().describe("Name of the bus to send output to"),
|
||||
rename: z.string().optional().describe("New name for the bus"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("set_audio_bus", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("add_audio_bus_effect", "Add an audio effect to a bus. Types: reverb, chorus, delay, compressor, limiter, phaser, distortion, lowpassfilter, highpassfilter, bandpassfilter, amplify, eq", {
|
||||
bus: z.string().describe("Name of the audio bus"),
|
||||
effect_type: z.string().describe("Effect type: reverb, chorus, delay, compressor, limiter, phaser, distortion, lowpassfilter (or lowpass), highpassfilter (or highpass), bandpassfilter (or bandpass), amplify, eq"),
|
||||
params: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional().describe("Effect-specific parameters. E.g. for reverb: {room_size, damping, wet, dry, spread}; for compressor: {threshold, ratio, attack_us, release_ms}; for filters: {cutoff_hz, resonance}"),
|
||||
at_position: z.number().optional().describe("Effect index position (-1 = end)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("add_audio_bus_effect", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("add_audio_player", "Add an AudioStreamPlayer, AudioStreamPlayer2D, or AudioStreamPlayer3D node to a parent node", {
|
||||
node_path: z.string().describe("Path to the parent node"),
|
||||
name: z.string().describe("Name for the new audio player node"),
|
||||
type: z.string().optional().describe("Player type: AudioStreamPlayer (default), AudioStreamPlayer2D, AudioStreamPlayer3D"),
|
||||
stream: z.string().optional().describe("Path to audio resource (e.g. 'res://audio/music.ogg')"),
|
||||
volume_db: z.number().optional().describe("Volume in dB (default: 0)"),
|
||||
bus: z.string().optional().describe("Audio bus name (default: 'Master')"),
|
||||
autoplay: z.boolean().optional().describe("Auto-play when scene starts (default: false)"),
|
||||
max_distance: z.number().optional().describe("Maximum hearing distance (for 2D/3D players)"),
|
||||
attenuation: z.number().optional().describe("Distance attenuation factor (for 2D players)"),
|
||||
attenuation_model: z.number().optional().describe("Attenuation model for 3D: 0=inverse_distance, 1=inverse_square, 2=logarithmic"),
|
||||
unit_size: z.number().optional().describe("Unit size for 3D player volume reference"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("add_audio_player", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_audio_info", "Get audio setup for a node subtree: finds all AudioStreamPlayer nodes with their settings, streams, and bus assignments", {
|
||||
node_path: z.string().describe("Path to the root node to search within"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_audio_info", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=audio-tools.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { GodotConnection } from "../godot-connection.js";
|
||||
export declare function registerBatchTools(server: McpServer, godot: GodotConnection): void;
|
||||
//# sourceMappingURL=batch-tools.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"batch-tools.d.ts","sourceRoot":"","sources":["../../src/tools/batch-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,eAAe,GACrB,IAAI,CA+HN"}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { z } from "zod";
|
||||
import { formatErrorForMcp } from "../utils/errors.js";
|
||||
export function registerBatchTools(server, godot) {
|
||||
server.tool("find_nodes_by_type", "Find all nodes of a specific type in the current scene", {
|
||||
type: z.string().describe("Node type/class to search for (e.g. 'Sprite2D', 'Label', 'CollisionShape2D')"),
|
||||
recursive: z.boolean().optional().describe("Search recursively through children (default: true)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("find_nodes_by_type", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("find_signal_connections", "Find all signal connections in the current scene, optionally filtered by signal name or node", {
|
||||
signal_name: z.string().optional().describe("Filter by signal name (partial match)"),
|
||||
node_path: z.string().optional().describe("Filter by node path (partial match)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("find_signal_connections", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("batch_set_property", "Set a property on all nodes of a given type in the current scene", {
|
||||
type: z.string().describe("Node type to target (e.g. 'Label', 'Sprite2D')"),
|
||||
property: z.string().describe("Property name to set (e.g. 'visible', 'modulate')"),
|
||||
value: z.union([z.string(), z.number(), z.boolean()]).describe("Value to set. Strings auto-parsed for Vector2, Color, etc."),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("batch_set_property", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("batch_add_nodes", "Add multiple nodes in a single call. Supports building entire node trees at once — nodes added earlier can be referenced as parents by later entries. Much faster than calling add_node repeatedly.", {
|
||||
nodes: z.array(z.object({
|
||||
type: z.string().describe("Node type (e.g. 'Sprite2D', 'CharacterBody2D', 'Label')"),
|
||||
parent_path: z.string().optional().describe("Parent node path (default: root '.'). Can reference nodes created earlier in this batch."),
|
||||
name: z.string().optional().describe("Node name"),
|
||||
properties: z.record(z.string(), z.any()).optional().describe("Properties to set (e.g. {\"position\": \"Vector2(100, 200)\"})"),
|
||||
})).describe("Array of node definitions to add, processed in order"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("batch_add_nodes", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("find_node_references", "Search through project files (.tscn, .gd, .tres, .gdshader) for a text pattern", {
|
||||
pattern: z.string().describe("Text pattern to search for"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("find_node_references", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_scene_dependencies", "Get all resource dependencies of a scene or resource file", {
|
||||
path: z.string().describe("Path to the scene or resource file (e.g. 'res://scenes/player.tscn')"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_scene_dependencies", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("cross_scene_set_property", "Preview or apply a property change on all nodes of a given type across scene files in the project. Defaults to dry_run=true (returns the matching scenes and node paths without writing). To actually apply: pass force=true AND dry_run=false. Inactive open scenes are skipped and reported in skipped_open_scenes — open them as the active tab first to live-edit. The active open scene is live-edited via UndoRedo so changes are visible in the editor and undoable. Closed scenes are offline-saved. The response includes a per-scene `mode` field: dry_run / offline_saved / live_open_scene.", {
|
||||
type: z.string().describe("Node type to target (e.g. 'Label', 'Sprite2D')"),
|
||||
property: z.string().describe("Property name to set"),
|
||||
value: z.union([z.string(), z.number(), z.boolean()]).describe("Value to set. Strings auto-parsed for Vector2, Color, etc."),
|
||||
path_filter: z.string().optional().describe("Directory to search in (default: 'res://')"),
|
||||
exclude_addons: z.boolean().optional().describe("Exclude addons/ directory (default: true)"),
|
||||
dry_run: z.boolean().optional().describe("Preview only — list affected scenes and nodes without writing. Defaults to true unless force=true is set."),
|
||||
force: z.boolean().optional().describe("Required (alongside dry_run=false) to actually write. Acknowledges that this can modify many scene files at once."),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("cross_scene_set_property", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=batch-tools.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { GodotConnection } from "../godot-connection.js";
|
||||
export declare function registerEditorTools(server: McpServer, godot: GodotConnection): void;
|
||||
//# sourceMappingURL=editor-tools.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"editor-tools.d.ts","sourceRoot":"","sources":["../../src/tools/editor-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,eAAe,GACrB,IAAI,CA2SN"}
|
||||
@@ -0,0 +1,231 @@
|
||||
import { z } from "zod";
|
||||
import { formatErrorForMcp } from "../utils/errors.js";
|
||||
export function registerEditorTools(server, godot) {
|
||||
server.tool("get_editor_errors", "Get recent errors and stack traces from the Godot editor log", {
|
||||
max_lines: z.number().optional().describe("Maximum log lines to scan for errors (default: 50)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_editor_errors", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_output_log", "Read the full Godot editor Output panel content. Unlike get_editor_errors which filters for errors only, this returns all output including print() statements and warnings.", {
|
||||
max_lines: z.number().optional().describe("Maximum number of lines to return from the end (default: 100)"),
|
||||
filter: z.string().optional().describe("Filter lines containing this substring (case-sensitive)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_output_log", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_editor_screenshot", "Capture a screenshot of the Godot editor's 2D/3D viewport", {
|
||||
save_path: z.string().optional().describe("Optional res:// or user:// path to save the screenshot as PNG file (e.g. 'res://screenshot.png'). When provided, the image is saved to disk and the file path is returned instead of base64 data."),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_editor_screenshot", params);
|
||||
if (result && typeof result === "object" && "saved_path" in result) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Screenshot saved: ${result.saved_path} (${result.width}x${result.height})`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
if (result && typeof result === "object" && "image_base64" in result) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "image",
|
||||
data: result.image_base64,
|
||||
mimeType: "image/png",
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `Screenshot captured: ${result.width}x${result.height}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_game_screenshot", "Capture a single screenshot of the running game (requires a scene to be playing). Good for checking static visual state (UI layout, scene composition, colors). For verifying animations or movement, use capture_frames instead — a single screenshot cannot confirm whether an animation is playing.", {
|
||||
save_path: z.string().optional().describe("Optional res:// or user:// path to save the screenshot as PNG file (e.g. 'res://screenshot.png'). When provided, the image is saved to disk and the file path is returned instead of base64 data."),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_game_screenshot", params);
|
||||
if (result && typeof result === "object" && "saved_path" in result) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Game screenshot saved: ${result.saved_path} (${result.width}x${result.height})${result.note ? ` (${result.note})` : ""}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
if (result && typeof result === "object" && "image_base64" in result) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "image",
|
||||
data: result.image_base64,
|
||||
mimeType: "image/png",
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: `Game screenshot: ${result.width}x${result.height}${result.note ? ` (${result.note})` : ""}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("execute_editor_script", "Execute arbitrary GDScript code inside the Godot editor. Use _mcp_print() to output values. By default refuses to execute code that contains direct file/resource write APIs (ResourceSaver.save, FileAccess WRITE, ProjectSettings.save, ConfigFile.save, DirAccess filesystem mutations) because those bypass the per-command open-resource guards. Use the dedicated MCP tools (save_scene, create_script, etc.) for those operations, or pass allow_unsafe_editor_io=true ONLY when you have verified no open editor resource will be overwritten.", {
|
||||
code: z.string().describe("GDScript code to execute. Use _mcp_print(value) to capture output. " +
|
||||
"The code runs inside a run() function with access to the full editor API."),
|
||||
allow_unsafe_editor_io: z.boolean().optional().describe("Override the file-write safety guard. Only set this when you are certain no open scene/script/shader will be overwritten by the script. Prefer the dedicated MCP tools for ordinary save flows."),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("execute_editor_script", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("clear_output", "Clear the Godot editor output panel", {}, async () => {
|
||||
try {
|
||||
const result = await godot.sendCommand("clear_output");
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_signals", "Get all signals of a node, including current connections", {
|
||||
node_path: z.string().describe("Path to the node to inspect"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_signals", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("reload_plugin", "Reload the Godot MCP Pro plugin (disable/re-enable). Connection will briefly drop and auto-reconnect. NOTE: This does NOT reload GDScript preload() caches. If you changed GDScript command files, use execute_editor_script with 'EditorInterface.restart_editor(true)' instead for a full editor restart.", {}, async () => {
|
||||
try {
|
||||
const result = await godot.sendCommand("reload_plugin");
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("reload_project", "Rescan the Godot project filesystem and reload changed scripts (no reconnection needed)", {}, async () => {
|
||||
try {
|
||||
const result = await godot.sendCommand("reload_project");
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("compare_screenshots", "Compare two screenshots pixel-by-pixel and return a diff analysis. Returns changed pixel count, diff percentage, and a highlighted diff image. Useful for visual regression testing. Accepts file paths (res://, user://) or base64 PNG strings.", {
|
||||
image_a: z.string().describe("First image: file path (e.g. 'user://screenshot_a.png') or base64 PNG string"),
|
||||
image_b: z.string().describe("Second image: file path (e.g. 'user://screenshot_b.png') or base64 PNG string"),
|
||||
threshold: z.number().optional().describe("Color difference threshold (0-255, default: 10). Pixels with max channel difference below this are considered identical."),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("compare_screenshots", params);
|
||||
const content = [];
|
||||
// Add summary text
|
||||
content.push({
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
identical: result.identical,
|
||||
changed_pixels: result.changed_pixels,
|
||||
total_pixels: result.total_pixels,
|
||||
diff_percentage: result.diff_percentage,
|
||||
threshold: result.threshold,
|
||||
size: `${result.width}x${result.height}`,
|
||||
}, null, 2),
|
||||
});
|
||||
// Add diff image if there are differences
|
||||
if (result.diff_image_base64 && !result.identical) {
|
||||
content.push({
|
||||
type: "image",
|
||||
data: result.diff_image_base64,
|
||||
mimeType: "image/png",
|
||||
});
|
||||
}
|
||||
return { content };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("set_auto_dismiss", "Enable or disable automatic dismissal of blocking editor dialogs (e.g. 'Reload from disk?', 'Save changes?'). Enable this before operations that modify files externally, and disable when done. Disabled by default.", {
|
||||
enabled: z.boolean().describe("true to enable auto-dismiss, false to disable"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("set_auto_dismiss", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_editor_camera", "Get the current 3D editor viewport camera position, rotation, and FOV. Use this to understand the current view before taking editor screenshots.", {}, async () => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_editor_camera");
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("set_editor_camera", "Move the 3D editor viewport camera to a specific position and orientation. Use this to frame a view before taking editor screenshots to validate changes visually.", {
|
||||
position: z.object({
|
||||
x: z.number().describe("X position"),
|
||||
y: z.number().describe("Y position"),
|
||||
z: z.number().describe("Z position"),
|
||||
}).optional().describe("Camera world position"),
|
||||
rotation_degrees: z.object({
|
||||
x: z.number().describe("Pitch (degrees)"),
|
||||
y: z.number().describe("Yaw (degrees)"),
|
||||
z: z.number().describe("Roll (degrees)"),
|
||||
}).optional().describe("Camera rotation in degrees"),
|
||||
look_at: z.object({
|
||||
x: z.number().describe("Target X"),
|
||||
y: z.number().describe("Target Y"),
|
||||
z: z.number().describe("Target Z"),
|
||||
}).optional().describe("Point to look at (overrides rotation_degrees if both set)"),
|
||||
fov: z.number().optional().describe("Field of view in degrees (default: 75)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("set_editor_camera", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=editor-tools.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { GodotConnection } from "../godot-connection.js";
|
||||
export declare function registerExportTools(server: McpServer, godot: GodotConnection): void;
|
||||
//# sourceMappingURL=export-tools.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"export-tools.d.ts","sourceRoot":"","sources":["../../src/tools/export-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,eAAe,GACrB,IAAI,CA8CN"}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { z } from "zod";
|
||||
import { formatErrorForMcp } from "../utils/errors.js";
|
||||
export function registerExportTools(server, godot) {
|
||||
server.tool("list_export_presets", "List all export presets configured in export_presets.cfg", {}, async () => {
|
||||
try {
|
||||
const result = await godot.sendCommand("list_export_presets");
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("export_project", "Get the export command for a preset (direct export from editor is not supported in Godot 4)", {
|
||||
preset_name: z.string().optional().describe("Export preset name"),
|
||||
preset_index: z.number().optional().describe("Export preset index (alternative to name)"),
|
||||
debug: z.boolean().optional().describe("Debug export (default: true)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("export_project", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_export_info", "Get export-related project info (executable path, templates, project path)", {}, async () => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_export_info");
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=export-tools.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"export-tools.js","sourceRoot":"","sources":["../../src/tools/export-tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,UAAU,mBAAmB,CACjC,MAAiB,EACjB,KAAsB;IAEtB,MAAM,CAAC,IAAI,CACT,qBAAqB,EACrB,0DAA0D,EAC1D,EAAE,EACF,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,qBAAqB,CAAC,CAAC;YAC9D,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,gBAAgB,EAChB,6FAA6F,EAC7F;QACE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC;QACjE,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2CAA2C,CAAC;QACzF,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,8BAA8B,CAAC;KACvE,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;YACjE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,iBAAiB,EACjB,4EAA4E,EAC5E,EAAE,EACF,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,iBAAiB,CAAC,CAAC;YAC1D,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { GodotConnection } from "../godot-connection.js";
|
||||
export declare function registerInputMapTools(server: McpServer, godot: GodotConnection): void;
|
||||
//# sourceMappingURL=input-map-tools.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"input-map-tools.d.ts","sourceRoot":"","sources":["../../src/tools/input-map-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,eAAe,GACrB,IAAI,CA8CN"}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { z } from "zod";
|
||||
import { formatErrorForMcp } from "../utils/errors.js";
|
||||
export function registerInputMapTools(server, godot) {
|
||||
server.tool("get_input_actions", "Get all input actions defined in the project's Input Map with their key/button bindings", {
|
||||
filter: z.string().optional().describe("Filter action names containing this substring"),
|
||||
include_builtin: z.boolean().optional().describe("Include built-in ui_* actions (default: false)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_input_actions", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("set_input_action", "Create or update an input action with key/mouse/joypad bindings. Saves to project.godot and updates the runtime InputMap.", {
|
||||
action: z.string().describe("Action name (e.g. 'move_left', 'jump', 'attack')"),
|
||||
events: z.array(z.object({
|
||||
type: z.enum(["key", "mouse_button", "joypad_button", "joypad_motion"]).describe("Event type"),
|
||||
keycode: z.string().optional().describe("Key name for 'key' type (e.g. 'W', 'Space', 'Escape', 'Shift')"),
|
||||
physical_keycode: z.string().optional().describe("Physical key name for 'key' type"),
|
||||
ctrl: z.boolean().optional().describe("Ctrl modifier for 'key' type"),
|
||||
shift: z.boolean().optional().describe("Shift modifier for 'key' type"),
|
||||
alt: z.boolean().optional().describe("Alt modifier for 'key' type"),
|
||||
meta: z.boolean().optional().describe("Meta/Cmd modifier for 'key' type"),
|
||||
button_index: z.number().optional().describe("Button index for mouse/joypad button types"),
|
||||
axis: z.number().optional().describe("Axis index for joypad_motion type"),
|
||||
axis_value: z.number().optional().describe("Axis value (-1.0 or 1.0) for joypad_motion type"),
|
||||
})).describe("Array of input event bindings"),
|
||||
deadzone: z.number().optional().describe("Deadzone for analog inputs (default: 0.5)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("set_input_action", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=input-map-tools.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"input-map-tools.js","sourceRoot":"","sources":["../../src/tools/input-map-tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,UAAU,qBAAqB,CACnC,MAAiB,EACjB,KAAsB;IAEtB,MAAM,CAAC,IAAI,CACT,mBAAmB,EACnB,yFAAyF,EACzF;QACE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;QACvF,eAAe,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gDAAgD,CAAC;KACnG,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;YACpE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,kBAAkB,EAClB,2HAA2H,EAC3H;QACE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kDAAkD,CAAC;QAC/E,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;YACvB,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC;YAC9F,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gEAAgE,CAAC;YACzG,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;YACpF,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,8BAA8B,CAAC;YACrE,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+BAA+B,CAAC;YACvE,GAAG,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6BAA6B,CAAC;YACnE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;YACzE,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,4CAA4C,CAAC;YAC1F,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,mCAAmC,CAAC;YACzE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iDAAiD,CAAC;SAC9F,CAAC,CAAC,CAAC,QAAQ,CAAC,+BAA+B,CAAC;QAC7C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2CAA2C,CAAC;KACtF,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;YACnE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { GodotConnection } from "../godot-connection.js";
|
||||
export declare function registerInputTools(server: McpServer, godot: GodotConnection): void;
|
||||
//# sourceMappingURL=input-tools.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"input-tools.d.ts","sourceRoot":"","sources":["../../src/tools/input-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAIzD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,eAAe,GACrB,IAAI,CAgIN"}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { z } from "zod";
|
||||
import { formatErrorForMcp } from "../utils/errors.js";
|
||||
import { coerceNumber } from "../utils/zod-coerce.js";
|
||||
export function registerInputTools(server, godot) {
|
||||
server.tool("simulate_key", "Simulate a keyboard key press or release in the running game. Use `duration` to hold a key for a set time (auto-releases after). Without duration: keys are NOT auto-released — you must explicitly call with pressed=false to release them.", {
|
||||
keycode: z.string().describe("Key constant (e.g. 'KEY_SPACE', 'KEY_W', 'KEY_ESCAPE')"),
|
||||
pressed: z.boolean().optional().describe("true for press, false for release (default: true)"),
|
||||
duration: coerceNumber().optional().describe("Hold duration in seconds (e.g. 1.5). Key is pressed, held for this duration, then auto-released. Cannot be used with pressed=false."),
|
||||
shift: z.boolean().optional().describe("Shift modifier (default: false)"),
|
||||
ctrl: z.boolean().optional().describe("Ctrl modifier (default: false)"),
|
||||
alt: z.boolean().optional().describe("Alt modifier (default: false)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
if (params.duration !== undefined && params.duration > 0) {
|
||||
const { duration, ...keyParams } = params;
|
||||
// Press
|
||||
await godot.sendCommand("simulate_key", { ...keyParams, pressed: true });
|
||||
// Hold
|
||||
await new Promise(resolve => setTimeout(resolve, duration * 1000));
|
||||
// Release
|
||||
await godot.sendCommand("simulate_key", { ...keyParams, pressed: false });
|
||||
return { content: [{ type: "text", text: JSON.stringify({
|
||||
event: { keycode: params.keycode, duration, shift: params.shift, ctrl: params.ctrl, alt: params.alt, auto_released: true },
|
||||
sent: true
|
||||
}, null, 2) }] };
|
||||
}
|
||||
const result = await godot.sendCommand("simulate_key", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("simulate_mouse_click", "Simulate a mouse button click at a position in the running game. By default sends both press and release (auto_release) so UI buttons work correctly.", {
|
||||
x: z.number().optional().describe("X position in viewport (default: 0)"),
|
||||
y: z.number().optional().describe("Y position in viewport (default: 0)"),
|
||||
button: z.number().optional().describe("Mouse button index: 1=left, 2=right, 3=middle (default: 1)"),
|
||||
pressed: z.boolean().optional().describe("true for press, false for release (default: true)"),
|
||||
double_click: z.boolean().optional().describe("Double click (default: false)"),
|
||||
auto_release: z.boolean().optional().describe("Auto-send release after press so buttons fire (default: true). Set false for drag operations."),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("simulate_mouse_click", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("simulate_mouse_move", "Simulate mouse movement in the running game. Use x/y for absolute viewport positioning (UI interaction), or relative_x/relative_y for relative motion (camera rotation in 3D games, FPS-style look). For 3D camera rotation: relative_x rotates yaw (negative = look left, positive = look right), relative_y rotates pitch (negative = look up, positive = look down). Typical values: 200-400px for a ~90° turn. Use navigate_to tool to calculate exact relative_x needed to face a target.", {
|
||||
x: z.number().optional().describe("Absolute X position in viewport (for UI interaction)"),
|
||||
y: z.number().optional().describe("Absolute Y position in viewport (for UI interaction)"),
|
||||
relative_x: z.number().optional().describe("Relative X movement in pixels. For 3D camera: negative = look left, positive = look right. ~400px ≈ 180° turn"),
|
||||
relative_y: z.number().optional().describe("Relative Y movement in pixels. For 3D camera: negative = look up, positive = look down"),
|
||||
button_mask: z.number().optional().describe("Mouse button mask to simulate drag. 1=left button held, 2=right button held, 4=middle button held. Required for drag operations like camera pan. (default: 0)"),
|
||||
unhandled: z.boolean().optional().describe("Force event to bypass GUI layer and go directly to _unhandled_input(). Auto-enabled when button_mask > 0. Use for camera pan/drag when UI overlays consume mouse events. (default: false)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("simulate_mouse_move", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("simulate_action", "Simulate a Godot Input Action (e.g. 'jump', 'move_left') in the running game", {
|
||||
action: z.string().describe("Action name as defined in Input Map (e.g. 'jump', 'move_left')"),
|
||||
pressed: z.boolean().optional().describe("true for press, false for release (default: true)"),
|
||||
strength: z.number().optional().describe("Action strength 0.0-1.0 (default: 1.0)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("simulate_action", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("simulate_sequence", "Simulate a sequence of input events with optional frame delays between them. Useful for complex input patterns like press W → wait 30 frames → press Space → wait → release all. After the sequence, use capture_frames to verify the visual result.", {
|
||||
events: z.array(z.object({
|
||||
type: z.string().describe("Event type: 'key', 'mouse_button', 'mouse_motion', or 'action'"),
|
||||
keycode: z.string().optional().describe("For 'key': key constant (e.g. 'KEY_SPACE')"),
|
||||
action: z.string().optional().describe("For 'action': action name"),
|
||||
button: z.number().optional().describe("For 'mouse_button': button index"),
|
||||
pressed: z.boolean().optional().describe("Press state (default: true)"),
|
||||
x: z.number().optional().describe("X position for mouse events"),
|
||||
y: z.number().optional().describe("Y position for mouse events"),
|
||||
relative_x: z.number().optional().describe("Relative X for mouse_motion"),
|
||||
relative_y: z.number().optional().describe("Relative Y for mouse_motion"),
|
||||
button_mask: z.number().optional().describe("Mouse button mask for mouse_motion drag: 1=left, 2=right, 4=middle"),
|
||||
unhandled: z.boolean().optional().describe("Bypass GUI, send directly to _unhandled_input. Auto-enabled for mouse_motion with button_mask > 0"),
|
||||
shift: z.boolean().optional(),
|
||||
ctrl: z.boolean().optional(),
|
||||
alt: z.boolean().optional(),
|
||||
strength: z.number().optional(),
|
||||
double_click: z.boolean().optional(),
|
||||
})).describe("Array of input events to send"),
|
||||
frame_delay: z.number().optional().describe("Frames to wait between events (default: 1, 0 = all in one frame)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("simulate_sequence", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=input-tools.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { GodotConnection } from "../godot-connection.js";
|
||||
export declare function registerNavigationTools(server: McpServer, godot: GodotConnection): void;
|
||||
//# sourceMappingURL=navigation-tools.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"navigation-tools.d.ts","sourceRoot":"","sources":["../../src/tools/navigation-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,eAAe,GACrB,IAAI,CA2GN"}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { z } from "zod";
|
||||
import { formatErrorForMcp } from "../utils/errors.js";
|
||||
export function registerNavigationTools(server, godot) {
|
||||
server.tool("setup_navigation_region", "Add a NavigationRegion2D/3D child to a node with auto-created NavigationPolygon or NavigationMesh. Auto-detects 2D/3D from parent context.", {
|
||||
node_path: z.string().describe("Path to the parent node to add the region to"),
|
||||
mode: z.string().optional().describe("Force '2d' or '3d' mode, or 'auto' to detect from parent (default: auto)"),
|
||||
name: z.string().optional().describe("Name for the NavigationRegion node"),
|
||||
navigation_layers: z.number().optional().describe("Navigation layers bitmask"),
|
||||
agent_radius: z.number().optional().describe("Agent radius for mesh generation (3D default: 0.5, 2D: from NavigationPolygon)"),
|
||||
agent_height: z.number().optional().describe("Agent height (3D only, default: 1.5)"),
|
||||
agent_max_climb: z.number().optional().describe("Max climb height (3D only, default: 0.25)"),
|
||||
agent_max_slope: z.number().optional().describe("Max slope angle in degrees (3D only, default: 45.0)"),
|
||||
cell_size: z.number().optional().describe("Cell size for navigation mesh (default: 0.25 for 3D)"),
|
||||
cell_height: z.number().optional().describe("Cell height (3D only, default: 0.25)"),
|
||||
source_geometry_mode: z.string().optional().describe("2D only: root_node, groups_with_children, or groups_explicit"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("setup_navigation_region", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("bake_navigation_mesh", "Bake navigation mesh for a NavigationRegion3D, or set outline vertices and generate polygons for a NavigationRegion2D.", {
|
||||
node_path: z.string().describe("Path to the NavigationRegion2D or NavigationRegion3D node"),
|
||||
outline: z.array(z.union([
|
||||
z.array(z.number()).describe("[x, y] coordinate pair"),
|
||||
z.object({ x: z.number(), y: z.number() }),
|
||||
])).optional().describe("2D only: Array of outline vertices as [x,y] pairs or {x,y} objects. At least 3 vertices required."),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("bake_navigation_mesh", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("setup_navigation_agent", "Add a NavigationAgent2D/3D child to a node and configure pathfinding and avoidance properties. Auto-detects 2D/3D from parent context.", {
|
||||
node_path: z.string().describe("Path to the parent node to add the agent to"),
|
||||
mode: z.string().optional().describe("Force '2d' or '3d' mode, or 'auto' to detect from parent (default: auto)"),
|
||||
name: z.string().optional().describe("Name for the NavigationAgent node"),
|
||||
path_desired_distance: z.number().optional().describe("Distance threshold to advance to next path point"),
|
||||
target_desired_distance: z.number().optional().describe("Distance threshold to consider target reached"),
|
||||
radius: z.number().optional().describe("Agent radius for avoidance"),
|
||||
neighbor_distance: z.number().optional().describe("Max distance to consider other agents as neighbors"),
|
||||
max_neighbors: z.number().optional().describe("Max number of neighbors for avoidance"),
|
||||
max_speed: z.number().optional().describe("Maximum movement speed for avoidance"),
|
||||
avoidance_enabled: z.boolean().optional().describe("Enable avoidance behavior"),
|
||||
navigation_layers: z.number().optional().describe("Navigation layers bitmask for pathfinding queries"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("setup_navigation_agent", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("set_navigation_layers", "Set navigation layers for a NavigationRegion or NavigationAgent. Supports bitmask value, layer bit numbers, or named layers from ProjectSettings.", {
|
||||
node_path: z.string().describe("Path to a NavigationRegion2D/3D or NavigationAgent2D/3D node"),
|
||||
layers: z.number().optional().describe("Navigation layers as a bitmask value (e.g. 5 = layers 1 and 3)"),
|
||||
layer_bits: z.array(z.number()).optional().describe("Array of 1-based layer numbers to enable (e.g. [1, 3] = bitmask 5)"),
|
||||
layer_names: z.array(z.string()).optional().describe("Array of named layer names from ProjectSettings (layer_names/2d_navigation/layer_N or 3d)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("set_navigation_layers", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_navigation_info", "Get navigation setup info for a node and its subtree: all NavigationRegions, NavigationAgents, their layers, and mesh/polygon data.", {
|
||||
node_path: z.string().describe("Path to the root node to inspect"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_navigation_info", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=navigation-tools.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { GodotConnection } from "../godot-connection.js";
|
||||
export declare function registerNodeTools(server: McpServer, godot: GodotConnection): void;
|
||||
//# sourceMappingURL=node-tools.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"node-tools.d.ts","sourceRoot":"","sources":["../../src/tools/node-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,eAAe,GACrB,IAAI,CA2SN"}
|
||||
@@ -0,0 +1,219 @@
|
||||
import { z } from "zod";
|
||||
import { formatErrorForMcp } from "../utils/errors.js";
|
||||
export function registerNodeTools(server, godot) {
|
||||
server.tool("add_node", "Add a new node to the current scene. Supports built-in Godot types and script-defined classes (class_name).", {
|
||||
type: z.string().describe("Node type — built-in (e.g. 'Sprite2D', 'Camera2D') or script class_name (e.g. 'HoverDetector', 'StationBuilder')"),
|
||||
parent_path: z.string().optional().describe("Parent node path (default: root '.')"),
|
||||
name: z.string().optional().describe("Node name"),
|
||||
properties: z.record(z.string(), z.any()).optional().describe("Properties to set (e.g. {\"position\": \"Vector2(100, 200)\"})"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("add_node", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("delete_node", "Delete a node from the current scene (supports undo)", {
|
||||
node_path: z.string().describe("Path to the node to delete"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("delete_node", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("duplicate_node", "Duplicate a node and all its children in the current scene", {
|
||||
node_path: z.string().describe("Path to the node to duplicate"),
|
||||
name: z.string().optional().describe("Name for the duplicate (default: original_copy)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("duplicate_node", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("move_node", "Move/reparent a node to a new parent in the scene tree", {
|
||||
node_path: z.string().describe("Path to the node to move"),
|
||||
new_parent_path: z.string().describe("Path to the new parent node"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("move_node", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("update_property", "Change a property on any node. Supports Vector2, Color, and other Godot types via string parsing.", {
|
||||
node_path: z.string().describe("Path to the target node"),
|
||||
property: z.string().describe("Property name (e.g. 'position', 'modulate', 'visible')"),
|
||||
value: z.any().describe("New value. Strings are auto-parsed: 'Vector2(10,20)', 'Color(1,0,0)', '#ff0000', etc."),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("update_property", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_node_properties", "Get all editor-visible properties of a node with their current values", {
|
||||
node_path: z.string().describe("Path to the node"),
|
||||
category: z.string().optional().describe("Filter by property category prefix (e.g. 'transform', 'texture')"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_node_properties", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("add_resource", "Add a resource (Shape2D, Material, Texture, etc.) to a node's property", {
|
||||
node_path: z.string().describe("Path to the target node"),
|
||||
property: z.string().describe("Property to set the resource on (e.g. 'shape', 'material', 'texture')"),
|
||||
resource_type: z.string().describe("Resource class name (e.g. 'RectangleShape2D', 'CircleShape2D', 'StandardMaterial3D')"),
|
||||
resource_properties: z.record(z.string(), z.any()).optional().describe("Properties to set on the created resource"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("add_resource", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("set_anchor_preset", "Set a Control node's anchor preset (e.g. full_rect, center, top_left)", {
|
||||
node_path: z.string().describe("Path to the Control node"),
|
||||
preset: z.string().describe("Anchor preset name: top_left, top_right, bottom_left, bottom_right, center_left, center_top, center_right, center_bottom, center, left_wide, top_wide, right_wide, bottom_wide, vcenter_wide, hcenter_wide, full_rect"),
|
||||
keep_offsets: z.boolean().optional().describe("Keep current offsets (default: false)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("set_anchor_preset", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("rename_node", "Rename a node in the current scene", {
|
||||
node_path: z.string().describe("Path to the node to rename"),
|
||||
new_name: z.string().describe("New name for the node"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("rename_node", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("connect_signal", "Connect a signal from one node to a method on another node. The connection is persistent (saved into the .tscn on save_scene).", {
|
||||
source_path: z.string().describe("Path to the source node (emitter)"),
|
||||
signal_name: z.string().describe("Signal name to connect"),
|
||||
target_path: z.string().describe("Path to the target node (receiver)"),
|
||||
method_name: z.string().describe("Method name on target to call"),
|
||||
deferred: z.boolean().optional().describe("Use a deferred connection (CONNECT_DEFERRED)"),
|
||||
one_shot: z.boolean().optional().describe("Disconnect automatically after the first emission (CONNECT_ONE_SHOT)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("connect_signal", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("disconnect_signal", "Disconnect a signal connection between two nodes", {
|
||||
source_path: z.string().describe("Path to the source node (emitter)"),
|
||||
signal_name: z.string().describe("Signal name to disconnect"),
|
||||
target_path: z.string().describe("Path to the target node (receiver)"),
|
||||
method_name: z.string().describe("Method name on target"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("disconnect_signal", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_node_groups", "Get all groups a node belongs to (excludes internal groups starting with '_')", {
|
||||
node_path: z.string().describe("Path to the node"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_node_groups", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("set_node_groups", "Set the groups a node belongs to. Computes diff with current groups and adds/removes as needed.", {
|
||||
node_path: z.string().describe("Path to the node"),
|
||||
groups: z.array(z.string()).describe("Desired list of group names (replaces current groups)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("set_node_groups", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("find_nodes_in_group", "Find all nodes in the current scene that belong to a specific group", {
|
||||
group: z.string().describe("Group name to search for"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("find_nodes_in_group", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_editor_selection", "Get the nodes currently selected in the editor Scene dock", {
|
||||
top_only: z.boolean().optional().describe("Return only the topmost selected nodes, excluding a node whose parent is already selected (default: false)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_editor_selection", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("select_nodes", "Select one or more nodes in the editor Scene dock, optionally focusing and inspecting them", {
|
||||
node_path: z.string().optional().describe("Path to a single node to select"),
|
||||
node_paths: z.array(z.string()).optional().describe("Paths to multiple nodes to select (use instead of node_path)"),
|
||||
mode: z.enum(["replace", "add", "remove"]).optional().describe("How to apply the selection (default: replace)"),
|
||||
inspect: z.boolean().optional().describe("Show the node in the Inspector (only applied when a single node is selected; default: true)"),
|
||||
focus: z.boolean().optional().describe("Focus the node in the Scene dock (only applied when a single node is selected; default: follows inspect)"),
|
||||
for_property: z.string().optional().describe("Inspector property to focus on"),
|
||||
inspector_only: z.boolean().optional().describe("Show in Inspector without changing the edited node (default: false)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("select_nodes", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("clear_editor_selection", "Clear the current editor Scene-dock selection", {}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("clear_editor_selection", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=node-tools.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { GodotConnection } from "../godot-connection.js";
|
||||
export declare function registerParticleTools(server: McpServer, godot: GodotConnection): void;
|
||||
//# sourceMappingURL=particle-tools.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"particle-tools.d.ts","sourceRoot":"","sources":["../../src/tools/particle-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,eAAe,GACrB,IAAI,CA8HN"}
|
||||
@@ -0,0 +1,106 @@
|
||||
import { z } from "zod";
|
||||
import { formatErrorForMcp } from "../utils/errors.js";
|
||||
export function registerParticleTools(server, godot) {
|
||||
server.tool("create_particles", "Add a GPUParticles2D or GPUParticles3D node with a ParticleProcessMaterial. Configure amount, lifetime, one_shot, explosiveness, and randomness.", {
|
||||
parent_path: z.string().describe("Path to the parent node to add particles to"),
|
||||
name: z.string().optional().describe("Name for the particles node (default: 'Particles')"),
|
||||
is_3d: z.boolean().optional().describe("Create GPUParticles3D instead of GPUParticles2D (default: false)"),
|
||||
amount: z.number().optional().describe("Number of particles (default: 16)"),
|
||||
lifetime: z.number().optional().describe("Particle lifetime in seconds (default: 1.0)"),
|
||||
one_shot: z.boolean().optional().describe("Emit only once (default: false)"),
|
||||
explosiveness: z.number().optional().describe("Explosiveness ratio 0-1 (default: 0.0)"),
|
||||
randomness: z.number().optional().describe("Randomness ratio 0-1 (default: 0.0)"),
|
||||
emitting: z.boolean().optional().describe("Start emitting immediately (default: true)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("create_particles", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("set_particle_material", "Configure ParticleProcessMaterial properties: direction, spread, velocity, gravity, scale, color, emission shape (point/sphere/box/ring), angular/orbit velocity, damping, and attractor interaction.", {
|
||||
node_path: z.string().describe("Path to the GPUParticles2D/3D node"),
|
||||
direction: z.object({
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
z: z.number(),
|
||||
}).optional().describe("Emission direction vector"),
|
||||
spread: z.number().optional().describe("Spread angle in degrees (0-180)"),
|
||||
initial_velocity_min: z.number().optional().describe("Minimum initial velocity"),
|
||||
initial_velocity_max: z.number().optional().describe("Maximum initial velocity"),
|
||||
gravity: z.object({
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
z: z.number(),
|
||||
}).optional().describe("Gravity vector"),
|
||||
scale_min: z.number().optional().describe("Minimum particle scale"),
|
||||
scale_max: z.number().optional().describe("Maximum particle scale"),
|
||||
color: z.string().optional().describe("Particle color (hex '#RRGGBB' or named color)"),
|
||||
emission_shape: z.string().optional().describe("Emission shape: point, sphere, sphere_surface, box, ring"),
|
||||
emission_sphere_radius: z.number().optional().describe("Sphere emission radius"),
|
||||
emission_box_extents: z.object({
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
z: z.number(),
|
||||
}).optional().describe("Box emission extents"),
|
||||
emission_ring_radius: z.number().optional().describe("Ring outer radius"),
|
||||
emission_ring_inner_radius: z.number().optional().describe("Ring inner radius"),
|
||||
emission_ring_height: z.number().optional().describe("Ring height"),
|
||||
angular_velocity_min: z.number().optional().describe("Minimum angular velocity (degrees/sec)"),
|
||||
angular_velocity_max: z.number().optional().describe("Maximum angular velocity (degrees/sec)"),
|
||||
orbit_velocity_min: z.number().optional().describe("Minimum orbit velocity"),
|
||||
orbit_velocity_max: z.number().optional().describe("Maximum orbit velocity"),
|
||||
damping_min: z.number().optional().describe("Minimum damping"),
|
||||
damping_max: z.number().optional().describe("Maximum damping"),
|
||||
attractor_interaction_enabled: z.boolean().optional().describe("Enable attractor interaction"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("set_particle_material", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("set_particle_color_gradient", "Set a color ramp (gradient) on a particle system's material. Provide an array of color stops with offset (0-1) and color.", {
|
||||
node_path: z.string().describe("Path to the GPUParticles2D/3D node"),
|
||||
stops: z.array(z.object({
|
||||
offset: z.number().describe("Gradient position (0.0 to 1.0)"),
|
||||
color: z.string().describe("Color at this stop (hex '#RRGGBB' or named color)"),
|
||||
})).describe("Array of gradient color stops"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("set_particle_color_gradient", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("apply_particle_preset", "Apply a named particle preset. Available presets: explosion (burst, short life), fire (upward, orange gradient), smoke (slow upward, gray), sparks (burst, high velocity), rain (downward, blue), snow (slow downward, drift), magic (orbit, colorful), dust (ambient, subtle).", {
|
||||
node_path: z.string().describe("Path to the GPUParticles2D/3D node"),
|
||||
preset: z.string().describe("Preset name: explosion, fire, smoke, sparks, rain, snow, magic, dust"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("apply_particle_preset", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_particle_info", "Get the full configuration of a particle system: node properties, material settings, emission shape, color gradient stops.", {
|
||||
node_path: z.string().describe("Path to the GPUParticles2D/3D node"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_particle_info", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=particle-tools.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { GodotConnection } from "../godot-connection.js";
|
||||
export declare function registerPhysicsTools(server: McpServer, godot: GodotConnection): void;
|
||||
//# sourceMappingURL=physics-tools.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"physics-tools.d.ts","sourceRoot":"","sources":["../../src/tools/physics-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,eAAe,GACrB,IAAI,CA4IN"}
|
||||
@@ -0,0 +1,115 @@
|
||||
import { z } from "zod";
|
||||
import { formatErrorForMcp } from "../utils/errors.js";
|
||||
export function registerPhysicsTools(server, godot) {
|
||||
server.tool("setup_collision", "Add a CollisionShape2D/3D child to a physics body or area node with a specified shape. Auto-detects 2D/3D from the parent node type.", {
|
||||
node_path: z.string().describe("Path to the parent physics body or area node (e.g. CharacterBody2D, StaticBody3D, Area2D)"),
|
||||
shape: z.string().describe("Shape type: 'rectangle'/'rect', 'circle', 'capsule', 'segment' (2D only), 'cylinder' (3D only), 'custom'/'convex'. For 3D: 'box'/'sphere' also work."),
|
||||
width: z.number().optional().describe("Width for rectangle/box shape (default: 32 for 2D, 1 for 3D)"),
|
||||
height: z.number().optional().describe("Height for rectangle/box/capsule/cylinder shape"),
|
||||
depth: z.number().optional().describe("Depth for 3D box shape (default: 1)"),
|
||||
radius: z.number().optional().describe("Radius for circle/sphere/capsule/cylinder shape"),
|
||||
ax: z.number().optional().describe("Segment start X (2D segment only)"),
|
||||
ay: z.number().optional().describe("Segment start Y (2D segment only)"),
|
||||
bx: z.number().optional().describe("Segment end X (2D segment only)"),
|
||||
by: z.number().optional().describe("Segment end Y (2D segment only)"),
|
||||
points: z.array(z.array(z.number())).optional().describe("Convex polygon points as [[x,y],...] for 2D or [[x,y,z],...] for 3D"),
|
||||
disabled: z.boolean().optional().describe("Create the collision shape disabled (default: false)"),
|
||||
one_way_collision: z.boolean().optional().describe("Enable one-way collision (2D only, default: false)"),
|
||||
dimension: z.string().optional().describe("Force '2d' or '3d' if auto-detection fails"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("setup_collision", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("set_physics_layers", "Set collision layer and/or mask on a physics body or area node. Supports bitmask integers or arrays of layer numbers.", {
|
||||
node_path: z.string().describe("Path to the node with collision layers"),
|
||||
collision_layer: z.union([z.number(), z.array(z.number())]).optional().describe("Collision layer: bitmask integer or array of layer numbers [1,3,5]"),
|
||||
collision_mask: z.union([z.number(), z.array(z.number())]).optional().describe("Collision mask: bitmask integer or array of layer numbers [1,2,4]"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("set_physics_layers", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_physics_layers", "Get the current collision layer and mask for a node, including named layer info from ProjectSettings.", {
|
||||
node_path: z.string().describe("Path to the node with collision layers"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_physics_layers", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("add_raycast", "Add a RayCast2D/3D child node for collision detection. Auto-detects 2D/3D from the parent node type.", {
|
||||
node_path: z.string().describe("Path to the parent node"),
|
||||
name: z.string().optional().describe("Name for the raycast node (default: 'RayCast')"),
|
||||
target_x: z.number().optional().describe("Target position X (default: 0)"),
|
||||
target_y: z.number().optional().describe("Target position Y (default: 50 for 2D, -1 for 3D)"),
|
||||
target_z: z.number().optional().describe("Target position Z (3D only, default: 0)"),
|
||||
collision_mask: z.number().optional().describe("Collision mask bitmask (default: 1)"),
|
||||
enabled: z.boolean().optional().describe("Enable the raycast (default: true)"),
|
||||
collide_with_areas: z.boolean().optional().describe("Collide with Area nodes (default: false)"),
|
||||
collide_with_bodies: z.boolean().optional().describe("Collide with physics bodies (default: true)"),
|
||||
hit_from_inside: z.boolean().optional().describe("Detect hits from inside shapes (default: false)"),
|
||||
dimension: z.string().optional().describe("Force '2d' or '3d' if auto-detection fails"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("add_raycast", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("setup_physics_body", "Configure physics body properties. For CharacterBody2D/3D: floor settings, motion mode, etc. For RigidBody2D/3D: mass, gravity, damping, etc.", {
|
||||
node_path: z.string().describe("Path to the physics body node"),
|
||||
// CharacterBody properties
|
||||
floor_stop_on_slope: z.boolean().optional().describe("CharacterBody: stop on slopes when not moving"),
|
||||
floor_max_angle: z.number().optional().describe("CharacterBody: maximum floor angle in radians (default ~0.785 = 45 degrees)"),
|
||||
floor_snap_length: z.number().optional().describe("CharacterBody: floor snap distance for sticking to the ground"),
|
||||
wall_min_slide_angle: z.number().optional().describe("CharacterBody: minimum angle for wall sliding in radians"),
|
||||
motion_mode: z.string().optional().describe("CharacterBody: 'grounded' or 'floating'"),
|
||||
max_slides: z.number().optional().describe("CharacterBody: maximum slide iterations (default: 6)"),
|
||||
slide_on_ceiling: z.boolean().optional().describe("CharacterBody: allow sliding on ceiling"),
|
||||
// RigidBody properties
|
||||
mass: z.number().optional().describe("RigidBody: mass in kg (default: 1)"),
|
||||
gravity_scale: z.number().optional().describe("RigidBody: gravity multiplier (default: 1, 0 = no gravity)"),
|
||||
linear_damp: z.number().optional().describe("RigidBody: linear velocity damping"),
|
||||
angular_damp: z.number().optional().describe("RigidBody: angular velocity damping"),
|
||||
freeze: z.boolean().optional().describe("RigidBody: freeze the body (stop physics simulation)"),
|
||||
freeze_mode: z.string().optional().describe("RigidBody: 'static' or 'kinematic' freeze behavior"),
|
||||
continuous_cd: z.union([z.string(), z.boolean()]).optional().describe("RigidBody: continuous collision detection. 2D: 'disabled'/'cast_ray'/'cast_shape'. 3D: true/false"),
|
||||
contact_monitor: z.boolean().optional().describe("RigidBody: enable contact monitoring for body_entered/body_exited signals"),
|
||||
max_contacts_reported: z.number().optional().describe("RigidBody: max contacts to report (requires contact_monitor)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("setup_physics_body", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_collision_info", "Get detailed collision information for a node: all collision shapes, layers/masks, raycasts, and physics body settings. Scans children by default.", {
|
||||
node_path: z.string().describe("Path to the node to inspect"),
|
||||
include_children: z.boolean().optional().describe("Include children in the scan (default: true)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_collision_info", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=physics-tools.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { GodotConnection } from "../godot-connection.js";
|
||||
export declare function registerProfilingTools(server: McpServer, godot: GodotConnection): void;
|
||||
//# sourceMappingURL=profiling-tools.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"profiling-tools.d.ts","sourceRoot":"","sources":["../../src/tools/profiling-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,eAAe,GACrB,IAAI,CA8BN"}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { z } from "zod";
|
||||
import { formatErrorForMcp } from "../utils/errors.js";
|
||||
export function registerProfilingTools(server, godot) {
|
||||
server.tool("get_performance_monitors", "Get the RUNNING GAME's performance monitors (FPS, memory, draw calls, physics, navigation, etc.). Requires a scene to be playing (play_scene). For editor-process metrics use get_editor_performance.", {
|
||||
category: z.string().optional().describe("Filter by category prefix: 'fps', 'memory', 'render', 'physics_2d', 'physics_3d', 'navigation'"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_performance_monitors", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_editor_performance", "Get a quick performance summary (FPS, frame time, draw calls, memory usage)", {}, async () => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_editor_performance");
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=profiling-tools.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"profiling-tools.js","sourceRoot":"","sources":["../../src/tools/profiling-tools.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,UAAU,sBAAsB,CACpC,MAAiB,EACjB,KAAsB;IAEtB,MAAM,CAAC,IAAI,CACT,0BAA0B,EAC1B,uMAAuM,EACvM;QACE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gGAAgG,CAAC;KAC3I,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,0BAA0B,EAAE,MAAM,CAAC,CAAC;YAC3E,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,wBAAwB,EACxB,6EAA6E,EAC7E,EAAE,EACF,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,wBAAwB,CAAC,CAAC;YACjE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpF,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { GodotConnection } from "../godot-connection.js";
|
||||
export declare function registerProjectTools(server: McpServer, godot: GodotConnection): void;
|
||||
//# sourceMappingURL=project-tools.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"project-tools.d.ts","sourceRoot":"","sources":["../../src/tools/project-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,eAAe,GACrB,IAAI,CA0KN"}
|
||||
@@ -0,0 +1,125 @@
|
||||
import { z } from "zod";
|
||||
import { formatErrorForMcp } from "../utils/errors.js";
|
||||
export function registerProjectTools(server, godot) {
|
||||
server.tool("get_project_info", "Get Godot project metadata including name, version, viewport settings, renderer, and autoloads", {}, async () => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_project_info");
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_filesystem_tree", "Get the project's file/directory tree with optional filtering by extension (e.g. *.gd, *.tscn)", {
|
||||
path: z.string().optional().describe("Root path to scan (default: res://)"),
|
||||
filter: z.string().optional().describe("Glob filter pattern (e.g. '*.gd', '*.tscn')"),
|
||||
max_depth: z.number().optional().describe("Maximum directory depth to scan (default: 10)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_filesystem_tree", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("search_files", "Search for files by name using fuzzy matching or glob patterns", {
|
||||
query: z.string().describe("Search query (fuzzy match or glob pattern)"),
|
||||
path: z.string().optional().describe("Root path to search (default: res://)"),
|
||||
file_type: z.string().optional().describe("Filter by file extension (e.g. 'gd', 'tscn')"),
|
||||
max_results: z.number().optional().describe("Maximum results to return (default: 50)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("search_files", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("search_in_files", "Search for text content inside project files (grep-like). Searches through GDScript, scenes, resources, shaders, and other text files. Skips addons/ and .godot/ directories.", {
|
||||
query: z.string().describe("Text to search for (plain text or regex pattern)"),
|
||||
path: z.string().optional().describe("Root path to search (default: res://)"),
|
||||
regex: z.boolean().optional().describe("Use regex matching (default: false)"),
|
||||
file_type: z.string().optional().describe("Filter by file extension (e.g. 'gd', 'tscn'). Default: all text files"),
|
||||
max_results: z.number().optional().describe("Maximum results to return (default: 50)"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("search_in_files", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("get_project_settings", "Read project.godot settings by section or specific key", {
|
||||
section: z.string().optional().describe("Settings section prefix (e.g. 'display/window')"),
|
||||
key: z.string().optional().describe("Specific setting key (e.g. 'display/window/size/viewport_width')"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_project_settings", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("set_project_setting", "Set a project setting value (e.g. viewport size, main scene). Saves to project.godot via the editor API.", {
|
||||
key: z.string().describe("Setting key (e.g. 'display/window/size/viewport_width', 'application/run/main_scene')"),
|
||||
value: z.union([z.string(), z.number(), z.boolean()]).describe("Value to set. Strings are auto-parsed for Vector2, bool, int, float."),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("set_project_setting", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("uid_to_project_path", "Convert a Godot UID (uid://...) to a project resource path (res://...)", {
|
||||
uid: z.string().describe("The UID string (e.g. 'uid://abc123')"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("uid_to_project_path", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("project_path_to_uid", "Convert a project resource path (res://...) to its UID (uid://...)", {
|
||||
path: z.string().describe("The resource path (e.g. 'res://scenes/player.tscn')"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("project_path_to_uid", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("add_autoload", "Add an autoload (singleton) to the project. The script/scene will be auto-loaded when the project starts.", {
|
||||
name: z.string().describe("Autoload name (e.g. 'GameManager', 'AudioManager')"),
|
||||
path: z.string().describe("Path to the script or scene file (e.g. 'res://scripts/autoload/game_manager.gd')"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("add_autoload", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
server.tool("remove_autoload", "Remove an autoload (singleton) from the project settings", {
|
||||
name: z.string().describe("Autoload name to remove (e.g. 'GameManager')"),
|
||||
}, async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("remove_autoload", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
}
|
||||
catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=project-tools.js.map
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user