breakpoint-mcp 1.21.1 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/annotations.js +225 -0
- package/dist/capabilities.js +5 -1
- package/dist/cli/doctor.js +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/tools.js +193 -0
- package/dist/index.js +26 -4
- package/dist/peers.js +238 -0
- package/dist/recipes.js +33 -1
- package/dist/schemas.js +17 -0
- package/dist/secret.js +49 -0
- package/dist/tools/processes.js +25 -1
- package/dist/tools/runtime.js +292 -67
- package/dist/toolsets.js +7 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
The MCP **host** for [Breakpoint MCP](https://github.com/jlivingston-Cipher/godot-breakpoint-mcp) —
|
|
4
4
|
a [Model Context Protocol](https://modelcontextprotocol.io) server that exposes the
|
|
5
5
|
Godot game engine to AI coding assistants across four planes: headless CLI, the live
|
|
6
|
-
editor, Godot's own LSP + DAP, and a runtime bridge inside the running game. **
|
|
6
|
+
editor, Godot's own LSP + DAP, and a runtime bridge inside the running game. **289 tools
|
|
7
7
|
+ 6 MCP resources**, built against the stable `@modelcontextprotocol/sdk` 1.x API.
|
|
8
8
|
Developed and tested with **Claude**; because MCP is an open protocol, other clients can
|
|
9
9
|
connect too (currently untested — reports welcome).
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/** Tools that mutate nothing. */
|
|
2
|
+
const READ_ONLY = [
|
|
3
|
+
"anim_get_track_keys", "anim_list", "backend_detect", "class_reference", "classdb_get_class", "cs_code_action",
|
|
4
|
+
"cs_completion", "cs_dbg_scopes", "cs_dbg_stack_trace", "cs_dbg_variables", "cs_definition", "cs_diagnostics",
|
|
5
|
+
"cs_document_symbols", "cs_hover", "cs_references", "cs_signature_help", "cs_workspace_symbols", "dbg_scopes",
|
|
6
|
+
"dbg_stack_trace", "dbg_variables", "docs_search", "editor_get_state", "editor_ping", "example_snippet",
|
|
7
|
+
"filesystem_list", "find_symbol", "find_usages", "gd_call_hierarchy", "gd_code_action", "gd_completion",
|
|
8
|
+
"gd_declaration", "gd_definition", "gd_diagnostics", "gd_document_color", "gd_document_highlight",
|
|
9
|
+
"gd_document_link", "gd_document_symbols", "gd_folding_ranges", "gd_formatting", "gd_hover", "gd_implementation",
|
|
10
|
+
"gd_references", "gd_semantic_tokens", "gd_signature_help", "gd_type_definition", "gd_workspace_symbols",
|
|
11
|
+
"godot_output", "godot_version", "inputmap_list", "node_find", "node_get_children", "node_get_path",
|
|
12
|
+
"node_get_property", "node_list_groups", "node_list_properties", "project_get_info", "project_get_setting",
|
|
13
|
+
"project_list_settings", "project_search", "resource_get_import_settings", "resource_get_property",
|
|
14
|
+
"resource_load", "runtime_anim_get_state", "runtime_assert_node_state", "runtime_assert_perf",
|
|
15
|
+
"runtime_assert_scene_structure", "runtime_assert_screen_text", "runtime_await_condition", "runtime_get_log",
|
|
16
|
+
"runtime_get_monitors", "runtime_get_property", "runtime_get_tree", "runtime_screenshot",
|
|
17
|
+
"runtime_peers_digest", "runtime_screenshot_diff", "runtime_state_digest", "scene_get_dependencies",
|
|
18
|
+
"scene_get_tree", "scene_list_open", "screenshot_editor", "selection_get", "signal_list",
|
|
19
|
+
"signal_list_connections", "test_detect", "test_list", "tilemap_get_cell", "vcs_blame", "vcs_branch_list",
|
|
20
|
+
"vcs_diff", "vcs_log", "vcs_show", "vcs_status",
|
|
21
|
+
];
|
|
22
|
+
/** Tools that may overwrite or discard state the caller did not supply. */
|
|
23
|
+
const DESTRUCTIVE = [
|
|
24
|
+
"anim_delete", "anim_remove_key", "asset_gen_audio_sfx", "asset_gen_icon", "asset_gen_model",
|
|
25
|
+
"asset_gen_placeholder", "asset_gen_sprite", "asset_gen_texture", "audio_bus_set_volume", "audio_set_bus_layout",
|
|
26
|
+
"auth_scaffold", "backend_configure", "board_create", "board_tile_create", "card_template_create",
|
|
27
|
+
"cloudsave_scaffold", "cs_dbg_evaluate", "cs_dbg_set_variable", "cs_rename", "dbg_evaluate", "dbg_goto",
|
|
28
|
+
"dbg_set_variable", "editorsettings_get_set", "environment_create", "environment_set_sky", "filesystem_move",
|
|
29
|
+
"gd_rename", "godot_run_headless_script", "godot_stop", "inputmap_add_action", "inputmap_erase_action",
|
|
30
|
+
"interact_add_drop_zone", "interact_make_draggable", "leaderboard_scaffold", "mp_scaffold_lobby",
|
|
31
|
+
"mp_setup_enet_peer", "mp_setup_webrtc_peer", "mp_wire_rpc", "node_call_method", "node_change_type",
|
|
32
|
+
"node_delete", "node_remove_from_group", "physics_set_gravity", "piece_template_create", "primitive_mesh_create",
|
|
33
|
+
"project_add_autoload", "project_remove_autoload", "project_set_main_scene", "project_set_setting",
|
|
34
|
+
"resource_create", "resource_duplicate", "resource_save", "resource_set_import_settings",
|
|
35
|
+
"resource_set_property", "runtime_anim_play", "runtime_anim_stop", "runtime_call_method", "runtime_emit_signal",
|
|
36
|
+
"runtime_inject_input", "runtime_node_remove", "runtime_peer_stop", "runtime_seed_rng", "runtime_set_property",
|
|
37
|
+
"runtime_step_frames", "runtime_time_scale", "scene_close", "scene_new", "scene_pack", "scene_reload", "scene_save_as", "shader_create",
|
|
38
|
+
"shader_set_code", "shadermaterial_create", "shadermaterial_set_param", "shadermaterial_set_shader",
|
|
39
|
+
"signal_disconnect", "signal_emit", "theme_create", "theme_set_color", "theme_set_constant", "theme_set_font",
|
|
40
|
+
"theme_set_stylebox", "tilemap_clear", "tilemap_set_cell", "tilemap_set_cells_rect", "tileset_create",
|
|
41
|
+
"vcs_restore", "vcs_stash",
|
|
42
|
+
];
|
|
43
|
+
/** Tools where a repeat call with identical arguments adds no further effect. */
|
|
44
|
+
const IDEMPOTENT = [
|
|
45
|
+
"anim_create", "anim_delete", "anim_get_track_keys", "anim_insert_key", "anim_list", "anim_set_length",
|
|
46
|
+
"anim_set_loop", "anim_statemachine_add_state", "anim_statemachine_add_transition", "anim_tree_add_node",
|
|
47
|
+
"area_set_gravity", "area_set_monitoring", "asset_gen_configure", "asset_gen_placeholder",
|
|
48
|
+
"audio_bus_set_volume", "audio_set_bus_layout", "audio_set_stream", "auth_scaffold", "backend_configure",
|
|
49
|
+
"backend_detect", "board_create", "board_place", "board_tile_create", "board_tile_place",
|
|
50
|
+
"body_set_collision_layer", "body_set_collision_mask", "body_set_physics_material", "card_template_create",
|
|
51
|
+
"class_reference", "classdb_get_class", "cloudsave_scaffold", "control_set_anchors", "control_set_layout_preset",
|
|
52
|
+
"control_set_size_flags", "control_set_theme", "cs_code_action", "cs_completion", "cs_dbg_attach",
|
|
53
|
+
"cs_dbg_scopes", "cs_dbg_set_breakpoints", "cs_dbg_set_exception_breakpoints", "cs_dbg_set_variable",
|
|
54
|
+
"cs_dbg_stack_trace", "cs_dbg_variables", "cs_dbg_watch", "cs_definition", "cs_diagnostics",
|
|
55
|
+
"cs_document_symbols", "cs_hover", "cs_references", "cs_rename", "cs_signature_help", "cs_workspace_symbols",
|
|
56
|
+
"dbg_attach", "dbg_data_breakpoints", "dbg_goto", "dbg_scopes", "dbg_set_breakpoints",
|
|
57
|
+
"dbg_set_exception_breakpoints", "dbg_set_variable", "dbg_stack_trace", "dbg_variables", "dbg_watch",
|
|
58
|
+
"docs_search", "editor_get_state", "editor_ping", "editorsettings_get_set", "environment_create",
|
|
59
|
+
"environment_set_sky", "example_snippet", "filesystem_create_dir", "filesystem_list", "filesystem_move",
|
|
60
|
+
"filesystem_scan", "find_symbol", "find_usages", "gd_call_hierarchy", "gd_code_action", "gd_completion",
|
|
61
|
+
"gd_declaration", "gd_definition", "gd_diagnostics", "gd_document_color", "gd_document_highlight",
|
|
62
|
+
"gd_document_link", "gd_document_symbols", "gd_folding_ranges", "gd_formatting", "gd_hover", "gd_implementation",
|
|
63
|
+
"gd_references", "gd_rename", "gd_semantic_tokens", "gd_signature_help", "gd_type_definition",
|
|
64
|
+
"gd_workspace_symbols", "godot_export", "godot_import", "godot_output", "godot_stop", "godot_version",
|
|
65
|
+
"inputmap_add_action", "inputmap_erase_action", "inputmap_list", "joint_set_bodies", "leaderboard_scaffold",
|
|
66
|
+
"mesh_set_surface_material", "mp_scaffold_lobby", "mp_set_authority", "mp_setup_enet_peer",
|
|
67
|
+
"mp_setup_webrtc_peer", "mp_wire_rpc", "node_add_to_group", "node_change_type", "node_delete", "node_find",
|
|
68
|
+
"node_get_children", "node_get_path", "node_get_property", "node_list_groups", "node_list_properties",
|
|
69
|
+
"node_move_child", "node_remove_from_group", "node_rename", "node_reparent", "node_set_editable_instance",
|
|
70
|
+
"node_set_owner", "node_set_property", "particles_set_amount", "particles_set_emitting",
|
|
71
|
+
"particles_set_lifetime", "particles_set_process_material", "particles_set_texture", "physics_set_gravity",
|
|
72
|
+
"piece_template_create", "primitive_mesh_create", "project_add_autoload", "project_get_info",
|
|
73
|
+
"project_get_setting", "project_list_settings", "project_remove_autoload", "project_search",
|
|
74
|
+
"project_set_main_scene", "project_set_setting", "resource_create", "resource_duplicate",
|
|
75
|
+
"resource_get_import_settings", "resource_get_property", "resource_load", "resource_save",
|
|
76
|
+
"resource_set_import_settings", "resource_set_property", "rigidbody_set_properties", "runtime_anim_get_state",
|
|
77
|
+
"runtime_anim_stop", "runtime_assert_node_state", "runtime_assert_perf", "runtime_assert_scene_structure",
|
|
78
|
+
"runtime_assert_screen_text", "runtime_await_condition", "runtime_get_log", "runtime_get_monitors",
|
|
79
|
+
"runtime_get_property", "runtime_get_tree", "runtime_node_remove", "runtime_peer_stop", "runtime_peers_digest",
|
|
80
|
+
"runtime_screenshot", "runtime_screenshot_diff", "runtime_seed_rng", "runtime_set_property",
|
|
81
|
+
"runtime_state_digest", "runtime_time_scale", "scene_get_dependencies", "scene_get_tree", "scene_list_open", "scene_new", "scene_open",
|
|
82
|
+
"scene_pack", "scene_reload", "scene_save", "scene_save_as", "screenshot_editor", "selection_get",
|
|
83
|
+
"selection_set", "shader_create", "shader_set_code", "shadermaterial_create", "shadermaterial_set_param",
|
|
84
|
+
"shadermaterial_set_shader", "signal_add_user_signal", "signal_connect", "signal_disconnect", "signal_list",
|
|
85
|
+
"signal_list_connections", "test_detect", "test_list", "theme_create", "theme_set_color", "theme_set_constant",
|
|
86
|
+
"theme_set_font", "theme_set_stylebox", "tilemap_clear", "tilemap_get_cell", "tilemap_set_cell",
|
|
87
|
+
"tilemap_set_cells_rect", "tileset_add_tile", "tileset_create", "vcs_add", "vcs_blame", "vcs_branch_list",
|
|
88
|
+
"vcs_diff", "vcs_log", "vcs_restore", "vcs_show", "vcs_status", "vcs_switch",
|
|
89
|
+
];
|
|
90
|
+
/**
|
|
91
|
+
* Tools that reach outside this machine + this project. Empty by design — every
|
|
92
|
+
* bridge is loopback-only. A future networked tool must be listed here.
|
|
93
|
+
*/
|
|
94
|
+
const OPEN_WORLD = [];
|
|
95
|
+
/**
|
|
96
|
+
* The complete annotated roster — every registered tool, listed explicitly.
|
|
97
|
+
*
|
|
98
|
+
* This CANNOT be derived as the union of the four lists above: 52 tools are
|
|
99
|
+
* all-false (mutating, non-destructive, non-idempotent, local — e.g. `node_add`,
|
|
100
|
+
* `dbg_step`, `vcs_commit`), so a derived union would silently omit them and the
|
|
101
|
+
* totality check below would pass while they shipped unannotated. Listing the
|
|
102
|
+
* roster explicitly is what makes "every tool is annotated" an assertion rather
|
|
103
|
+
* than a tautology.
|
|
104
|
+
*/
|
|
105
|
+
export const ALL_ANNOTATED = [
|
|
106
|
+
"anim_add_track", "anim_create", "anim_delete", "anim_get_track_keys", "anim_insert_key", "anim_list",
|
|
107
|
+
"anim_player_create", "anim_remove_key", "anim_set_length", "anim_set_loop", "anim_statemachine_add_state",
|
|
108
|
+
"anim_statemachine_add_transition", "anim_tree_add_node", "anim_tree_create", "area_set_gravity",
|
|
109
|
+
"area_set_monitoring", "asset_gen_audio_sfx", "asset_gen_configure", "asset_gen_icon", "asset_gen_model",
|
|
110
|
+
"asset_gen_placeholder", "asset_gen_sprite", "asset_gen_texture", "audio_bus_add", "audio_bus_add_effect",
|
|
111
|
+
"audio_bus_set_volume", "audio_player_create", "audio_set_bus_layout", "audio_set_stream", "auth_scaffold",
|
|
112
|
+
"backend_configure", "backend_detect", "board_create", "board_place", "board_tile_create", "board_tile_place",
|
|
113
|
+
"body_create", "body_set_collision_layer", "body_set_collision_mask", "body_set_physics_material",
|
|
114
|
+
"camera_create", "card_deck_from_table", "card_hand_layout", "card_instance", "card_set_face",
|
|
115
|
+
"card_template_create", "class_reference", "classdb_get_class", "cloudsave_scaffold", "collisionpolygon_add",
|
|
116
|
+
"collisionshape_add", "container_add_child", "control_create", "control_set_anchors",
|
|
117
|
+
"control_set_layout_preset", "control_set_size_flags", "control_set_theme", "cs_code_action", "cs_completion",
|
|
118
|
+
"cs_dbg_attach", "cs_dbg_continue", "cs_dbg_evaluate", "cs_dbg_launch", "cs_dbg_restart", "cs_dbg_scopes",
|
|
119
|
+
"cs_dbg_set_breakpoints", "cs_dbg_set_exception_breakpoints", "cs_dbg_set_variable", "cs_dbg_stack_trace",
|
|
120
|
+
"cs_dbg_step", "cs_dbg_variables", "cs_dbg_watch", "cs_definition", "cs_diagnostics", "cs_document_symbols",
|
|
121
|
+
"cs_hover", "cs_references", "cs_rename", "cs_signature_help", "cs_workspace_symbols", "csg_create",
|
|
122
|
+
"dbg_attach", "dbg_continue", "dbg_data_breakpoints", "dbg_evaluate", "dbg_goto", "dbg_launch", "dbg_restart",
|
|
123
|
+
"dbg_scopes", "dbg_set_breakpoints", "dbg_set_exception_breakpoints", "dbg_set_variable", "dbg_stack_trace",
|
|
124
|
+
"dbg_step", "dbg_variables", "dbg_watch", "docs_search", "editor_get_state", "editor_ping", "editor_redo",
|
|
125
|
+
"editor_undo", "editorsettings_get_set", "environment_create", "environment_set_sky", "example_snippet",
|
|
126
|
+
"filesystem_create_dir", "filesystem_list", "filesystem_move", "filesystem_scan", "find_symbol", "find_usages",
|
|
127
|
+
"gd_call_hierarchy", "gd_code_action", "gd_completion", "gd_declaration", "gd_definition", "gd_diagnostics",
|
|
128
|
+
"gd_document_color", "gd_document_highlight", "gd_document_link", "gd_document_symbols", "gd_folding_ranges",
|
|
129
|
+
"gd_formatting", "gd_hover", "gd_implementation", "gd_references", "gd_rename", "gd_semantic_tokens",
|
|
130
|
+
"gd_signature_help", "gd_type_definition", "gd_workspace_symbols", "godot_export", "godot_import",
|
|
131
|
+
"godot_launch_editor", "godot_output", "godot_run_headless_script", "godot_run_managed", "godot_run_project",
|
|
132
|
+
"godot_stop", "godot_version", "inputmap_add_action", "inputmap_add_event", "inputmap_erase_action",
|
|
133
|
+
"inputmap_list", "interact_add_drop_zone", "interact_make_draggable", "joint_create", "joint_set_bodies",
|
|
134
|
+
"leaderboard_scaffold", "light_create", "mesh_set_surface_material", "meshinstance_create", "mp_add_spawner",
|
|
135
|
+
"mp_add_synchronizer", "mp_scaffold_lobby", "mp_set_authority", "mp_setup_enet_peer", "mp_setup_webrtc_peer",
|
|
136
|
+
"mp_wire_rpc", "navagent_configure", "navregion_create", "node_add", "node_add_to_group", "node_call_method",
|
|
137
|
+
"node_change_type", "node_delete", "node_duplicate", "node_find", "node_get_children", "node_get_path",
|
|
138
|
+
"node_get_property", "node_instantiate_scene", "node_list_groups", "node_list_properties", "node_move_child",
|
|
139
|
+
"node_remove_from_group", "node_rename", "node_reparent", "node_set_editable_instance", "node_set_owner",
|
|
140
|
+
"node_set_property", "particles_create", "particles_set_amount", "particles_set_emitting",
|
|
141
|
+
"particles_set_lifetime", "particles_set_process_material", "particles_set_texture", "physics_set_gravity",
|
|
142
|
+
"piece_instance", "piece_move", "piece_template_create", "primitive_mesh_create", "project_add_autoload",
|
|
143
|
+
"project_add_export_preset", "project_get_info", "project_get_setting", "project_list_settings",
|
|
144
|
+
"project_remove_autoload", "project_search", "project_set_main_scene", "project_set_setting", "resource_create",
|
|
145
|
+
"resource_duplicate", "resource_get_import_settings", "resource_get_property", "resource_load", "resource_save",
|
|
146
|
+
"resource_set_import_settings", "resource_set_property", "rigidbody_set_properties", "runtime_anim_get_state",
|
|
147
|
+
"runtime_anim_play", "runtime_anim_stop", "runtime_assert_node_state", "runtime_assert_perf",
|
|
148
|
+
"runtime_assert_scene_structure", "runtime_assert_screen_text", "runtime_await_condition", "runtime_call_method",
|
|
149
|
+
"runtime_emit_signal", "runtime_get_log", "runtime_get_monitors", "runtime_get_property", "runtime_get_tree",
|
|
150
|
+
"runtime_inject_input", "runtime_node_add", "runtime_node_remove", "runtime_peer_stop", "runtime_peers_digest",
|
|
151
|
+
"runtime_screenshot", "runtime_screenshot_diff", "runtime_seed_rng", "runtime_set_property",
|
|
152
|
+
"runtime_spawn_peers", "runtime_state_digest", "runtime_step_frames", "runtime_time_scale", "scene_close",
|
|
153
|
+
"scene_get_dependencies", "scene_get_tree",
|
|
154
|
+
"scene_list_open", "scene_new", "scene_open", "scene_pack", "scene_reload", "scene_save", "scene_save_as",
|
|
155
|
+
"screenshot_editor", "selection_get", "selection_set", "shader_create", "shader_set_code",
|
|
156
|
+
"shadermaterial_create", "shadermaterial_set_param", "shadermaterial_set_shader", "signal_add_user_signal",
|
|
157
|
+
"signal_connect", "signal_disconnect", "signal_emit", "signal_list", "signal_list_connections", "test_detect",
|
|
158
|
+
"test_list", "theme_create", "theme_set_color", "theme_set_constant", "theme_set_font", "theme_set_stylebox",
|
|
159
|
+
"tilemap_clear", "tilemap_get_cell", "tilemap_set_cell", "tilemap_set_cells_rect", "tilemaplayer_create",
|
|
160
|
+
"tileset_add_source", "tileset_add_tile", "tileset_create", "tileset_set_tile_collision", "vcs_add", "vcs_blame",
|
|
161
|
+
"vcs_branch_create", "vcs_branch_list", "vcs_commit", "vcs_diff", "vcs_log", "vcs_restore", "vcs_show",
|
|
162
|
+
"vcs_stash", "vcs_status", "vcs_switch",
|
|
163
|
+
];
|
|
164
|
+
const readOnly = new Set(READ_ONLY);
|
|
165
|
+
const destructive = new Set(DESTRUCTIVE);
|
|
166
|
+
const idempotent = new Set(IDEMPOTENT);
|
|
167
|
+
const openWorld = new Set(OPEN_WORLD);
|
|
168
|
+
/**
|
|
169
|
+
* Guard the one way this table can rot silently: a name typo'd into READ_ONLY /
|
|
170
|
+
* DESTRUCTIVE / IDEMPOTENT that is not on the roster would otherwise annotate
|
|
171
|
+
* nothing and fail no test. Throwing at module load turns that into an
|
|
172
|
+
* immediate, obvious failure in every consumer, tests included.
|
|
173
|
+
*/
|
|
174
|
+
{
|
|
175
|
+
const roster = new Set(ALL_ANNOTATED);
|
|
176
|
+
const orphans = [...new Set([...READ_ONLY, ...DESTRUCTIVE, ...IDEMPOTENT, ...OPEN_WORLD])]
|
|
177
|
+
.filter((n) => !roster.has(n))
|
|
178
|
+
.sort();
|
|
179
|
+
if (orphans.length) {
|
|
180
|
+
throw new Error(`annotations.ts: name(s) in a hint list but absent from ALL_ANNOTATED: ${orphans.join(", ")}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/** Every tool name carrying an annotation entry — the totality set for the tests. */
|
|
184
|
+
export const ANNOTATED_TOOLS = Object.freeze([...ALL_ANNOTATED].sort());
|
|
185
|
+
/**
|
|
186
|
+
* The four hints for one tool. Every registered tool resolves to a complete
|
|
187
|
+
* block: absence from a list is a positive "false", never "unknown", so a
|
|
188
|
+
* consumer never has to fall back to guessing from the name.
|
|
189
|
+
*/
|
|
190
|
+
export function annotationsFor(name) {
|
|
191
|
+
return {
|
|
192
|
+
readOnlyHint: readOnly.has(name),
|
|
193
|
+
destructiveHint: destructive.has(name),
|
|
194
|
+
idempotentHint: idempotent.has(name),
|
|
195
|
+
openWorldHint: openWorld.has(name),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Wrap `server.registerTool` to inject the annotations block at registration.
|
|
200
|
+
* Mirrors `applyOutputSchemas` / `applyCapabilities`; call once, after
|
|
201
|
+
* `applyOutputSchemas` and before any `register*Tools()`. An explicit
|
|
202
|
+
* `annotations` already on the config wins, so a call site can override.
|
|
203
|
+
*
|
|
204
|
+
* `title` is left alone — the SDK already promotes the top-level `title` to the
|
|
205
|
+
* annotation of the same name, and duplicating it here would fight that.
|
|
206
|
+
*/
|
|
207
|
+
export function applyAnnotations(server) {
|
|
208
|
+
const inject = (raw) => (name, config, handler) => {
|
|
209
|
+
const cfg = config;
|
|
210
|
+
if (cfg && cfg.annotations === undefined) {
|
|
211
|
+
config = { ...cfg, annotations: annotationsFor(name) };
|
|
212
|
+
}
|
|
213
|
+
return raw(name, config, handler);
|
|
214
|
+
};
|
|
215
|
+
const s = server;
|
|
216
|
+
s.registerTool = inject(s.registerTool.bind(server));
|
|
217
|
+
// D2 task-model tools (godot_export / godot_import / godot_run_headless_script)
|
|
218
|
+
// register through experimental.tasks.registerToolTask, not registerTool —
|
|
219
|
+
// annotate that path too or three tools ship hint-less.
|
|
220
|
+
const tasks = s.experimental?.tasks;
|
|
221
|
+
if (tasks?.registerToolTask) {
|
|
222
|
+
tasks.registerToolTask = inject(tasks.registerToolTask.bind(tasks));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=annotations.js.map
|
package/dist/capabilities.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export const CAPABILITY_GROUPS = ["code-execution", "network"];
|
|
3
3
|
/** One-line human description per group (shown by `doctor` and the resource). */
|
|
4
4
|
export const GROUP_DESCRIBE = {
|
|
5
|
-
"code-execution": "Run arbitrary GDScript, invoke arbitrary methods, evaluate an expression in a paused debug frame, or run a local asset-gen command backend.",
|
|
5
|
+
"code-execution": "Run arbitrary GDScript, invoke arbitrary methods, evaluate an expression in a paused debug frame, spawn headless child processes of the project, or run a local asset-gen command backend.",
|
|
6
6
|
network: "Egress beyond loopback — the Group M backend SDK.",
|
|
7
7
|
};
|
|
8
8
|
/**
|
|
@@ -16,6 +16,10 @@ export const TOOL_CAPABILITIES = {
|
|
|
16
16
|
godot_run_managed: ["code-execution"],
|
|
17
17
|
node_call_method: ["code-execution"],
|
|
18
18
|
runtime_call_method: ["code-execution"],
|
|
19
|
+
// F6: spawns headless Godot child processes of the project — the same class of
|
|
20
|
+
// trust as godot_run_managed, which is why it is privileged rather than merely
|
|
21
|
+
// confirmation-gated.
|
|
22
|
+
runtime_spawn_peers: ["code-execution"],
|
|
19
23
|
dbg_evaluate: ["code-execution"],
|
|
20
24
|
cs_dbg_evaluate: ["code-execution"],
|
|
21
25
|
// asset generation — the local command backend (code-execution) is the only
|
package/dist/cli/doctor.js
CHANGED
|
@@ -193,7 +193,7 @@ export function checkCapabilities(config) {
|
|
|
193
193
|
status: "ok",
|
|
194
194
|
severity: "info",
|
|
195
195
|
detail: dropped.length === 0
|
|
196
|
-
? `${state} — full
|
|
196
|
+
? `${state} — full 289-tool surface`
|
|
197
197
|
: `${state} (secure default) — ${dropped.length} higher-trust tool(s) dropped from the surface`,
|
|
198
198
|
hint: dropped.length === 0
|
|
199
199
|
? undefined
|
package/dist/cli/init.js
CHANGED
|
@@ -279,7 +279,7 @@ export async function runInit(argv, deps = {}) {
|
|
|
279
279
|
// Trust groups — surface the secure default so even a bare `init` teaches it.
|
|
280
280
|
say("");
|
|
281
281
|
if (privilegedGroups) {
|
|
282
|
-
say(`Higher-trust tool groups enabled: ${privilegedGroups} — the full
|
|
282
|
+
say(`Higher-trust tool groups enabled: ${privilegedGroups} — the full 289-tool surface loads.`);
|
|
283
283
|
}
|
|
284
284
|
else {
|
|
285
285
|
say("Higher-trust tool groups are OFF by default (the secure default). The assistant loads the");
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `breakpoint-mcp tools` — export the tool surface as a stable, machine-readable
|
|
3
|
+
* artifact.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists: third-party MCP catalogs, policy gateways, and security
|
|
6
|
+
* reviewers need to know what this server exposes and how risky each tool is.
|
|
7
|
+
* Without a published artifact their only options are to run the server and
|
|
8
|
+
* speak MCP at it, or to infer risk from tool NAMES. A public catalog did the
|
|
9
|
+
* latter in July 2026 and got it backwards in both directions — `tilemap_clear`
|
|
10
|
+
* filed as irreversible (it is undoable), `theme_set_color` not flagged at all
|
|
11
|
+
* (it rewrites a `.tres` in place). This command, plus the annotations shipped
|
|
12
|
+
* on `tools/list`, removes the need to guess.
|
|
13
|
+
*
|
|
14
|
+
* It reports the surface WITHOUT connecting to Godot: the registry is built
|
|
15
|
+
* against a recorder using the same `buildToolsets` path `index.ts` drives, with
|
|
16
|
+
* stub clients, and no handler is ever invoked. So it works in CI, in a
|
|
17
|
+
* container, and on a machine with no Godot installed.
|
|
18
|
+
*
|
|
19
|
+
* Two surfaces are reported, because the difference is the security story:
|
|
20
|
+
* • `full` — all 289 tools, what loads with BREAKPOINT_PRIVILEGED_GROUPS=all
|
|
21
|
+
* • `secure-default` — 274, what an untouched install actually advertises
|
|
22
|
+
* `--surface` picks which one populates `tools[]`; both counts are always in the
|
|
23
|
+
* header so a consumer cannot report one as the other by accident.
|
|
24
|
+
*/
|
|
25
|
+
import { parseArgs } from "./args.js";
|
|
26
|
+
import { buildToolsets } from "../toolsets.js";
|
|
27
|
+
import { applyOutputSchemas } from "../schemas.js";
|
|
28
|
+
import { applyAnnotations, annotationsFor } from "../annotations.js";
|
|
29
|
+
import { TOOL_CAPABILITIES, CAPABILITY_GROUPS, GROUP_DESCRIBE } from "../capabilities.js";
|
|
30
|
+
import { loadConfig } from "../config.js";
|
|
31
|
+
import { readFileSync } from "node:fs";
|
|
32
|
+
import { fileURLToPath } from "node:url";
|
|
33
|
+
import { dirname, join } from "node:path";
|
|
34
|
+
/**
|
|
35
|
+
* Read the version from package.json at runtime rather than hardcoding it.
|
|
36
|
+
* Deliberate: the version already has to be bumped in `package.json`, the
|
|
37
|
+
* `serverInfo` literal, both `plugin.cfg`s, and the doctor/init surface strings
|
|
38
|
+
* — this command must not become a sixth place to forget. package.json ships in
|
|
39
|
+
* the npm tarball, so the read is safe in an installed package; if it ever
|
|
40
|
+
* isn't, an unknown version is better than a stale one.
|
|
41
|
+
*/
|
|
42
|
+
function packageVersion() {
|
|
43
|
+
try {
|
|
44
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
45
|
+
return JSON.parse(readFileSync(join(here, "..", "..", "package.json"), "utf8")).version ?? "unknown";
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return "unknown";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Register the whole surface against a recorder, exactly as `index.ts` does
|
|
53
|
+
* (`applyOutputSchemas` → `applyAnnotations` → every `register*Tools`), and
|
|
54
|
+
* capture each tool's config. Capability filtering is applied afterwards rather
|
|
55
|
+
* than via `applyCapabilities`, so a single pass can report both surfaces.
|
|
56
|
+
*/
|
|
57
|
+
function recordSurface() {
|
|
58
|
+
const recorded = [];
|
|
59
|
+
let current = "";
|
|
60
|
+
const push = (name, config) => {
|
|
61
|
+
recorded.push({ name, config, toolset: current });
|
|
62
|
+
return { name };
|
|
63
|
+
};
|
|
64
|
+
const server = {
|
|
65
|
+
registerTool: (name, config) => push(name, config),
|
|
66
|
+
registerResource: () => { },
|
|
67
|
+
experimental: {
|
|
68
|
+
tasks: { registerToolTask: (name, config) => push(name, config) },
|
|
69
|
+
},
|
|
70
|
+
server: { elicitInput: async () => ({ action: "decline" }) },
|
|
71
|
+
};
|
|
72
|
+
const mcp = server;
|
|
73
|
+
const stub = {};
|
|
74
|
+
applyOutputSchemas(mcp);
|
|
75
|
+
applyAnnotations(mcp);
|
|
76
|
+
for (const ts of buildToolsets({
|
|
77
|
+
server: mcp,
|
|
78
|
+
bridge: stub,
|
|
79
|
+
runtime: stub,
|
|
80
|
+
lsp: stub,
|
|
81
|
+
csLsp: stub,
|
|
82
|
+
dap: stub,
|
|
83
|
+
csDap: stub,
|
|
84
|
+
config: loadConfig(),
|
|
85
|
+
})) {
|
|
86
|
+
current = ts.id;
|
|
87
|
+
ts.run();
|
|
88
|
+
}
|
|
89
|
+
return recorded;
|
|
90
|
+
}
|
|
91
|
+
function toExported(r) {
|
|
92
|
+
const groups = [...(TOOL_CAPABILITIES[r.name] ?? [])];
|
|
93
|
+
const shape = r.config.inputSchema ?? {};
|
|
94
|
+
const params = Object.keys(shape);
|
|
95
|
+
return {
|
|
96
|
+
name: r.name,
|
|
97
|
+
title: typeof r.config.title === "string" ? r.config.title : null,
|
|
98
|
+
description: typeof r.config.description === "string" ? r.config.description : null,
|
|
99
|
+
toolset: r.toolset,
|
|
100
|
+
capabilityGroups: groups,
|
|
101
|
+
privileged: groups.length > 0,
|
|
102
|
+
confirmationGated: params.includes("confirm"),
|
|
103
|
+
params,
|
|
104
|
+
annotations: annotationsFor(r.name),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** Build the report. Exported for the tests, which assert it against the registry. */
|
|
108
|
+
export function buildToolsReport(surface) {
|
|
109
|
+
const all = recordSurface().map(toExported);
|
|
110
|
+
const secure = all.filter((t) => !t.privileged);
|
|
111
|
+
const shown = surface === "full" ? all : secure;
|
|
112
|
+
return {
|
|
113
|
+
server: { name: "breakpoint-mcp", version: packageVersion() },
|
|
114
|
+
// Deliberately null, not a timestamp: this artifact is a pure function of the
|
|
115
|
+
// source tree, so two runs of the same build must be byte-identical (a
|
|
116
|
+
// consumer can diff releases; CI can assert no drift).
|
|
117
|
+
generatedAt: null,
|
|
118
|
+
surface,
|
|
119
|
+
counts: {
|
|
120
|
+
full: all.length,
|
|
121
|
+
secureDefault: secure.length,
|
|
122
|
+
readOnly: shown.filter((t) => t.annotations.readOnlyHint).length,
|
|
123
|
+
destructive: shown.filter((t) => t.annotations.destructiveHint).length,
|
|
124
|
+
idempotent: shown.filter((t) => t.annotations.idempotentHint).length,
|
|
125
|
+
openWorld: shown.filter((t) => t.annotations.openWorldHint).length,
|
|
126
|
+
confirmationGated: shown.filter((t) => t.confirmationGated).length,
|
|
127
|
+
privileged: all.length - secure.length,
|
|
128
|
+
},
|
|
129
|
+
capabilityGroups: CAPABILITY_GROUPS.map((g) => ({
|
|
130
|
+
id: g,
|
|
131
|
+
describe: GROUP_DESCRIBE[g],
|
|
132
|
+
defaultEnabled: false,
|
|
133
|
+
tools: all.filter((t) => t.capabilityGroups.includes(g)).map((t) => t.name).sort(),
|
|
134
|
+
})),
|
|
135
|
+
tools: [...shown].sort((a, b) => a.name.localeCompare(b.name)),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function renderText(report) {
|
|
139
|
+
const c = report.counts;
|
|
140
|
+
const lines = [
|
|
141
|
+
`breakpoint-mcp ${report.server.version} — ${report.surface} surface`,
|
|
142
|
+
"",
|
|
143
|
+
` tools ${report.tools.length}` +
|
|
144
|
+
(report.surface === "secure-default" ? ` (of ${c.full} total; ${c.privileged} privileged, dropped)` : ""),
|
|
145
|
+
` read-only ${c.readOnly}`,
|
|
146
|
+
` destructive ${c.destructive}`,
|
|
147
|
+
` idempotent ${c.idempotent}`,
|
|
148
|
+
` open-world (egress) ${c.openWorld}`,
|
|
149
|
+
` confirmation-gated ${c.confirmationGated}`,
|
|
150
|
+
"",
|
|
151
|
+
"Capability groups (all OFF by default — enable with BREAKPOINT_PRIVILEGED_GROUPS):",
|
|
152
|
+
];
|
|
153
|
+
for (const g of report.capabilityGroups) {
|
|
154
|
+
lines.push(` ${g.id} (${g.tools.length} tools) — ${g.describe}`);
|
|
155
|
+
}
|
|
156
|
+
lines.push("", "Tools:");
|
|
157
|
+
for (const t of report.tools) {
|
|
158
|
+
const marks = [
|
|
159
|
+
t.annotations.readOnlyHint ? "read-only" : null,
|
|
160
|
+
t.annotations.destructiveHint ? "destructive" : null,
|
|
161
|
+
t.confirmationGated ? "gated" : null,
|
|
162
|
+
t.privileged ? `privileged:${t.capabilityGroups.join("+")}` : null,
|
|
163
|
+
].filter(Boolean);
|
|
164
|
+
lines.push(` ${t.name.padEnd(34)} ${marks.join(" · ")}`);
|
|
165
|
+
}
|
|
166
|
+
lines.push("", "Re-run with --json for the machine-readable form.", "");
|
|
167
|
+
return lines.join("\n");
|
|
168
|
+
}
|
|
169
|
+
export async function runTools(argv) {
|
|
170
|
+
const { flags } = parseArgs(argv, ["json", "help", "h"]);
|
|
171
|
+
if (flags.help || flags.h) {
|
|
172
|
+
process.stdout.write([
|
|
173
|
+
"breakpoint-mcp tools — export the tool surface (name, risk annotations, capability group).",
|
|
174
|
+
"",
|
|
175
|
+
" --json Emit JSON (stable, no timestamp — safe to diff between releases).",
|
|
176
|
+
" --surface <which> full | secure-default (default: secure-default, what an",
|
|
177
|
+
" untouched install actually advertises).",
|
|
178
|
+
"",
|
|
179
|
+
"Reads no Godot install and starts no server — the registry is built statically.",
|
|
180
|
+
"",
|
|
181
|
+
].join("\n"));
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
const raw = typeof flags.surface === "string" ? flags.surface : "secure-default";
|
|
185
|
+
if (raw !== "full" && raw !== "secure-default") {
|
|
186
|
+
process.stderr.write(`unknown --surface "${raw}" (expected: full | secure-default)\n`);
|
|
187
|
+
return 2;
|
|
188
|
+
}
|
|
189
|
+
const report = buildToolsReport(raw);
|
|
190
|
+
process.stdout.write(flags.json ? `${JSON.stringify(report, null, 2)}\n` : renderText(report));
|
|
191
|
+
return 0;
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=tools.js.map
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { DapClient } from "./dap.js";
|
|
|
12
12
|
import { buildToolsets } from "./toolsets.js";
|
|
13
13
|
import { registerRecipes } from "./recipes.js";
|
|
14
14
|
import { applyOutputSchemas } from "./schemas.js";
|
|
15
|
+
import { applyAnnotations } from "./annotations.js";
|
|
15
16
|
import { applyCapabilities, droppedTools, registerCapabilitiesResource, selectPrivilegedGroups, } from "./capabilities.js";
|
|
16
17
|
import { taskStore, TASK_CAPABILITIES } from "./tasks.js";
|
|
17
18
|
import { RESOURCE_CAPABILITIES, registerResourceSubscriptions } from "./subscriptions.js";
|
|
@@ -35,24 +36,31 @@ async function main() {
|
|
|
35
36
|
// so long jobs (export/import/headless script) support poll/await/cancel.
|
|
36
37
|
// D3: also advertise resources.subscribe so clients can subscribe to
|
|
37
38
|
// godot://… resources and receive notifications/resources/updated.
|
|
38
|
-
const server = new McpServer({ name: "breakpoint-mcp", version: "1.
|
|
39
|
+
const server = new McpServer({ name: "breakpoint-mcp", version: "1.23.0" }, { capabilities: { ...TASK_CAPABILITIES, ...RESOURCE_CAPABILITIES }, taskStore });
|
|
39
40
|
// B1: enforce frozen output schemas on every structured tool. Must run before
|
|
40
41
|
// any register*Tools call — it wraps server.registerTool.
|
|
41
42
|
applyOutputSchemas(server);
|
|
43
|
+
// MCP tool annotations — publish readOnly/destructive/idempotent/openWorld
|
|
44
|
+
// hints on every tool so clients and policy catalogs read our risk posture
|
|
45
|
+
// instead of guessing it from tool names. Hints only; the enforcement stays
|
|
46
|
+
// in applyCapabilities (below) + confirm.ts. Wraps registerTool after the
|
|
47
|
+
// schema wrapper, before any register*Tools call.
|
|
48
|
+
applyAnnotations(server);
|
|
42
49
|
// Capability groups — a risk-based axis over the toolsets. Both `code-execution`
|
|
43
50
|
// and `network` are OFF by default; a disabled group's tools are DROPPED at
|
|
44
51
|
// registration (omitted from tools/list), so the secure-default surface is
|
|
45
|
-
//
|
|
52
|
+
// 289 − 15 = 274 tools. Enable via BREAKPOINT_PRIVILEGED_GROUPS. Wraps
|
|
46
53
|
// server.registerTool AFTER applyOutputSchemas (schema wrapper stays innermost).
|
|
47
54
|
const privilegedGroups = selectPrivilegedGroups(config.privilegedGroups, (unknown) => log(`ignoring unknown BREAKPOINT_PRIVILEGED_GROUPS token(s): ${unknown.join(", ")}`));
|
|
48
55
|
applyCapabilities(server, privilegedGroups);
|
|
49
56
|
// The A/B/C/D planes ARE the grouping. Build the ordered toolset registry
|
|
50
57
|
// (the single source of truth, shared with the registration tests) and
|
|
51
58
|
// register only the selected groups. Default (BREAKPOINT_TOOLSETS unset) =
|
|
52
|
-
// every group → the full
|
|
59
|
+
// every group → the full 289-tool surface, byte-identical to before. A filter
|
|
53
60
|
// lets a client that can't defer tools, or a user who wants a smaller default
|
|
54
61
|
// menu, load only the planes a project needs (GitHub-MCP `--toolsets` style).
|
|
55
62
|
let processes;
|
|
63
|
+
let peers;
|
|
56
64
|
const toolsets = buildToolsets({
|
|
57
65
|
server,
|
|
58
66
|
bridge,
|
|
@@ -65,6 +73,9 @@ async function main() {
|
|
|
65
73
|
onProcesses: (h) => {
|
|
66
74
|
processes = h;
|
|
67
75
|
},
|
|
76
|
+
onPeers: (h) => {
|
|
77
|
+
peers = h;
|
|
78
|
+
},
|
|
68
79
|
});
|
|
69
80
|
const enabled = selectToolsets(toolsets.map((t) => t.id), config.toolsets, (unknown) => log(`ignoring unknown BREAKPOINT_TOOLSETS token(s): ${unknown.join(", ")}`));
|
|
70
81
|
for (const ts of toolsets)
|
|
@@ -85,7 +96,7 @@ async function main() {
|
|
|
85
96
|
log(`toolsets enabled: ${[...enabled].sort().join(", ")} (${enabled.size}/${toolsets.length} groups)`);
|
|
86
97
|
}
|
|
87
98
|
// Recipes: a free, curated task-recipe layer exposed as MCP prompts (discoverable
|
|
88
|
-
// via prompts/list). Adds NO tools — the
|
|
99
|
+
// via prompts/list). Adds NO tools — the 289-tool count is unchanged — and drives
|
|
89
100
|
// the enforced tools above, so it's a skill-pack layer over typed/undoable tools.
|
|
90
101
|
registerRecipes(server);
|
|
91
102
|
// Always-on capability affordance — never behind a toolset or a privileged
|
|
@@ -117,6 +128,8 @@ async function main() {
|
|
|
117
128
|
dap.close();
|
|
118
129
|
csDap.close();
|
|
119
130
|
processes?.killAll();
|
|
131
|
+
// F6: headless peers are children of this server — never outlive it.
|
|
132
|
+
peers?.stopAll();
|
|
120
133
|
process.exit(0);
|
|
121
134
|
};
|
|
122
135
|
process.on("SIGINT", shutdown);
|
|
@@ -130,6 +143,7 @@ function printUsage() {
|
|
|
130
143
|
" breakpoint-mcp Start the MCP server on stdio (default; how MCP clients launch it).",
|
|
131
144
|
" breakpoint-mcp init Install + enable the editor addon in a project and wire the MCP client.",
|
|
132
145
|
" breakpoint-mcp doctor Check the Godot binary, the editor addon, and the four bridges.",
|
|
146
|
+
" breakpoint-mcp tools Export the tool surface + risk annotations (for catalogs / reviews).",
|
|
133
147
|
" breakpoint-mcp --help Show this help.",
|
|
134
148
|
"",
|
|
135
149
|
"init options:",
|
|
@@ -147,6 +161,10 @@ function printUsage() {
|
|
|
147
161
|
" --timeout <ms> Per-bridge connect timeout (default 1500).",
|
|
148
162
|
" --json Emit the report as JSON.",
|
|
149
163
|
"",
|
|
164
|
+
"tools options:",
|
|
165
|
+
" --surface <which> full | secure-default (default: secure-default, what an untouched install advertises).",
|
|
166
|
+
" --json Emit the surface as JSON — stable and timestamp-free, so releases can be diffed.",
|
|
167
|
+
"",
|
|
150
168
|
"All runtime configuration is via environment variables; see the README.",
|
|
151
169
|
"",
|
|
152
170
|
].join("\n"));
|
|
@@ -160,6 +178,10 @@ void (async () => {
|
|
|
160
178
|
const { runDoctor } = await import("./cli/doctor.js");
|
|
161
179
|
process.exit(await runDoctor(process.argv.slice(3)));
|
|
162
180
|
}
|
|
181
|
+
if (sub === "tools") {
|
|
182
|
+
const { runTools } = await import("./cli/tools.js");
|
|
183
|
+
process.exit(await runTools(process.argv.slice(3)));
|
|
184
|
+
}
|
|
163
185
|
if (sub === "init") {
|
|
164
186
|
const { runInit } = await import("./cli/init.js");
|
|
165
187
|
process.exit(await runInit(process.argv.slice(3)));
|