breakpoint-mcp 1.12.0 → 1.13.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/addon/breakpoint_mcp/operations.gd +36 -0
- package/dist/schemas.js +2 -0
- package/dist/tools/editor/node.js +10 -0
- package/dist/tools/tabletop.js +15 -3
- 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. **270 tools
|
|
7
7
|
+ 5 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).
|
|
@@ -85,6 +85,8 @@ func dispatch(method: String, params: Dictionary) -> Dictionary:
|
|
|
85
85
|
return _node_change_type(params)
|
|
86
86
|
"node.set_owner":
|
|
87
87
|
return _node_set_owner(params)
|
|
88
|
+
"node.set_editable_instance":
|
|
89
|
+
return _node_set_editable_instance(params)
|
|
88
90
|
"node.call_method":
|
|
89
91
|
return _node_call_method(params)
|
|
90
92
|
"node.get_path":
|
|
@@ -1129,6 +1131,40 @@ func _node_set_owner(params: Dictionary) -> Dictionary:
|
|
|
1129
1131
|
return _ok({"path": _path_of(root, node), "owner": owner_out})
|
|
1130
1132
|
|
|
1131
1133
|
|
|
1134
|
+
## Toggle "Editable Children" on an instanced sub-scene. When enabled, property
|
|
1135
|
+
## overrides on the instance's INTERNAL nodes serialize into the owning scene on
|
|
1136
|
+
## save (otherwise the sub-scene is sealed and its internals revert on reload).
|
|
1137
|
+
## The flag lives on the owning scene's state, so it is set on the instance's
|
|
1138
|
+
## owner (the scene root that will save it), which is where is_editable_instance
|
|
1139
|
+
## reads it. This is what lets card_instance bake author-time slot data.
|
|
1140
|
+
func _node_set_editable_instance(params: Dictionary) -> Dictionary:
|
|
1141
|
+
var root := _edited_root()
|
|
1142
|
+
if root == null:
|
|
1143
|
+
return _err("no_scene", "No scene is open")
|
|
1144
|
+
var node := _resolve(root, String(params.get("path", "")))
|
|
1145
|
+
if node == null:
|
|
1146
|
+
return _err("bad_path", "Node not found: %s" % params.get("path", ""))
|
|
1147
|
+
if node == root:
|
|
1148
|
+
return _err("refused", "The scene root is not an instance")
|
|
1149
|
+
if node.scene_file_path == "":
|
|
1150
|
+
return _err("refused", "Node is not an instanced sub-scene (no scene_file_path); editable-instance applies only to scene instances")
|
|
1151
|
+
var owner_node: Node = node.owner
|
|
1152
|
+
if owner_node == null:
|
|
1153
|
+
return _err("refused", "Node has no owner; it is not saved into a scene, so editable-instance has nothing to record against")
|
|
1154
|
+
var editable := bool(params.get("editable", true))
|
|
1155
|
+
var old_editable := owner_node.is_editable_instance(node)
|
|
1156
|
+
var ur := _plugin.get_undo_redo()
|
|
1157
|
+
ur.create_action("Breakpoint: set editable-instance of %s" % node.name)
|
|
1158
|
+
ur.add_do_method(owner_node, "set_editable_instance", node, editable)
|
|
1159
|
+
ur.add_undo_method(owner_node, "set_editable_instance", node, old_editable)
|
|
1160
|
+
ur.commit_action()
|
|
1161
|
+
return _ok({
|
|
1162
|
+
"path": _path_of(root, node),
|
|
1163
|
+
"editable": owner_node.is_editable_instance(node),
|
|
1164
|
+
"owner": _path_of(root, owner_node),
|
|
1165
|
+
})
|
|
1166
|
+
|
|
1167
|
+
|
|
1132
1168
|
func _node_call_method(params: Dictionary) -> Dictionary:
|
|
1133
1169
|
var root := _edited_root()
|
|
1134
1170
|
if root == null:
|
package/dist/schemas.js
CHANGED
|
@@ -127,6 +127,7 @@ export const outputSchemas = {
|
|
|
127
127
|
node_move_child: { path: z.string(), index: z.number() },
|
|
128
128
|
node_change_type: { path: z.string(), name: z.string(), type: z.string(), old_type: z.string() },
|
|
129
129
|
node_set_owner: { path: z.string(), owner: z.string().nullable() },
|
|
130
|
+
node_set_editable_instance: { path: z.string(), editable: z.boolean(), owner: z.string() },
|
|
130
131
|
node_call_method: { path: z.string(), method: z.string(), result: encodedValue },
|
|
131
132
|
node_get_path: {
|
|
132
133
|
path: z.string(), name: z.string(), type: z.string(),
|
|
@@ -686,6 +687,7 @@ export const outputSchemas = {
|
|
|
686
687
|
face_up: z.boolean(),
|
|
687
688
|
bound: z.array(z.string()),
|
|
688
689
|
unbound: z.array(z.string()),
|
|
690
|
+
persisted: z.boolean(),
|
|
689
691
|
},
|
|
690
692
|
card_hand_layout: {
|
|
691
693
|
container_path: z.string(),
|
|
@@ -141,6 +141,16 @@ export function registerNodeTools(server, call) {
|
|
|
141
141
|
owner_path: z.string().optional().describe("Ancestor to own the node; \".\" or omitted for the scene root"),
|
|
142
142
|
},
|
|
143
143
|
}, async ({ path, owner_path }) => call("node.set_owner", owner_path !== undefined ? { path, owner_path } : { path }));
|
|
144
|
+
server.registerTool("node_set_editable_instance", {
|
|
145
|
+
title: "Set editable-instance (editable children)",
|
|
146
|
+
description: "Toggle \"Editable Children\" on an instanced sub-scene (undoable). When enabled, property overrides on the instance's " +
|
|
147
|
+
"internal nodes serialize into the saved scene; without it the sub-scene is sealed and its internals revert on reload. " +
|
|
148
|
+
"This is what lets author-time edits (e.g. card_instance slot data) be baked into the .tscn.",
|
|
149
|
+
inputSchema: {
|
|
150
|
+
path: z.string().describe("Node path of the instanced sub-scene, relative to the scene root"),
|
|
151
|
+
editable: z.boolean().optional().describe("Enable (true, default) or disable editable children"),
|
|
152
|
+
},
|
|
153
|
+
}, async ({ path, editable }) => call("node.set_editable_instance", { path, editable: editable ?? true }));
|
|
144
154
|
server.registerTool("node_call_method", {
|
|
145
155
|
title: "Call node method (edit-time)",
|
|
146
156
|
description: "Invoke a method on a node in the EDITED scene. DESTRUCTIVE (arbitrary invocation, not undoable) — gated by confirmation. " +
|
package/dist/tools/tabletop.js
CHANGED
|
@@ -479,16 +479,27 @@ async function emitOneCard(emit, args) {
|
|
|
479
479
|
}
|
|
480
480
|
const res = await emit("node.call_method", { path: instPath, method: "set_data", args: [args.data] });
|
|
481
481
|
await emit("node.call_method", { path: instPath, method: "set_face", args: [args.face_up] });
|
|
482
|
-
|
|
482
|
+
// Finding-A save-persistence (opt-in). set_data mutates the instance's internal
|
|
483
|
+
// slot nodes, but a sealed sub-scene doesn't serialize those overrides, so the
|
|
484
|
+
// data reverts on reload. Enabling "Editable Children" makes the overrides
|
|
485
|
+
// serialize into the saved scene → authored card data survives a reload.
|
|
486
|
+
// Default off preserves the lighter runtime-bound contract (cards populated at
|
|
487
|
+
// load via set_data), which is correct for gameplay decks.
|
|
488
|
+
const persisted = args.persist === true;
|
|
489
|
+
if (persisted) {
|
|
490
|
+
await emit("node.set_editable_instance", { path: instPath, editable: true });
|
|
491
|
+
}
|
|
492
|
+
return { instance_path: instPath, ...splitFromCall(res, args.data), persisted };
|
|
483
493
|
}
|
|
484
494
|
export async function emitCardInstance(emit, args) {
|
|
485
495
|
const face_up = args.face_up ?? true;
|
|
486
496
|
const name = args.name ?? sceneRootName(args.template_path);
|
|
487
|
-
const { instance_path, bound, unbound } = await emitOneCard(emit, {
|
|
497
|
+
const { instance_path, bound, unbound, persisted } = await emitOneCard(emit, {
|
|
488
498
|
template_path: args.template_path, parent: args.parent, data: args.data, name, face_up,
|
|
489
499
|
placement: args.position ? { x: args.position.x, y: args.position.y } : undefined,
|
|
500
|
+
persist: args.persist,
|
|
490
501
|
});
|
|
491
|
-
return { instance_path, face_up, bound, unbound };
|
|
502
|
+
return { instance_path, face_up, bound, unbound, persisted };
|
|
492
503
|
}
|
|
493
504
|
export async function emitCardHand(emit, args) {
|
|
494
505
|
const container = args.parent === "" ? "." : args.parent;
|
|
@@ -1380,6 +1391,7 @@ export function registerTabletopTools(server, bridge, config) {
|
|
|
1380
1391
|
position: z.object({ x: z.number(), y: z.number() }).optional().describe("Local position of the instance"),
|
|
1381
1392
|
face_up: z.boolean().optional().describe("Show the face (default true); false shows the back on two-sided cards"),
|
|
1382
1393
|
name: z.string().optional().describe("Optional node name for the instance"),
|
|
1394
|
+
persist: z.boolean().optional().describe("Bake the bound slot data into the saved scene by enabling Editable Children on the instance (default false = runtime-bound only, reverts on reload)"),
|
|
1383
1395
|
},
|
|
1384
1396
|
}, async (raw) => {
|
|
1385
1397
|
const a = raw;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "breakpoint-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "MCP server exposing Godot to AI coding assistants over the Model Context Protocol — developed and tested with Claude — across four planes (CLI, live editor, LSP+DAP, runtime) with elicitation-gated destructive tools, long jobs on the formal MCP task model, captured console output, and MCP resources.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|