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 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. **258 tools
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. " +
@@ -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
- return { instance_path: instPath, ...splitFromCall(res, args.data) };
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.12.0",
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",