breakpoint-mcp 1.4.1 → 1.12.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. **244 tools
6
+ editor, Godot's own LSP + DAP, and a runtime bridge inside the running game. **258 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).
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 James Livingston and breakpoint-mcp contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -18,6 +18,11 @@ var _server: TCPServer
18
18
  var _ops: Operations
19
19
  var _clients: Array = [] # Array of {peer: StreamPeerTCP, buf: String}
20
20
  var _port: int = DEFAULT_PORT
21
+ ## Re-entrancy guard for `_process` (Finding D). True while a request dispatch is
22
+ ## on the stack, so a handler that pumps the editor main loop (e.g. `scene.save`
23
+ ## triggers a filesystem rescan/reimport) cannot re-enter `_process` and re-drain
24
+ ## the same still-buffered line, which would recurse until the stack overflows.
25
+ var _dispatching: bool = false
21
26
 
22
27
 
23
28
  func setup(plugin: EditorPlugin) -> void:
@@ -64,6 +69,16 @@ func get_status() -> Dictionary:
64
69
  func _process(_delta: float) -> void:
65
70
  if _server == null:
66
71
  return
72
+ # Finding D: a request handler can pump the editor main loop (e.g. scene.save
73
+ # runs a filesystem rescan/reimport), which makes the engine call _process
74
+ # again *inside* the current dispatch. A client's line is only cleared from
75
+ # c["buf"] after _drain_lines returns, so a re-entrant tick would re-read and
76
+ # re-dispatch the SAME line, recursing until the stack overflows
77
+ # (operations.gd:512 _scene_save <-> _drain_lines/_handle_line here). Skip
78
+ # re-entrant ticks; buffered bytes are serviced on the next top-level tick.
79
+ if _dispatching:
80
+ return
81
+ _dispatching = true
67
82
  # Accept new connections.
68
83
  while _server.is_connection_available():
69
84
  var peer := _server.take_connection()
@@ -86,6 +101,7 @@ func _process(_delta: float) -> void:
86
101
  _drain_lines(c)
87
102
  still_alive.append(c)
88
103
  _clients = still_alive
104
+ _dispatching = false
89
105
 
90
106
 
91
107
  func _drain_lines(c: Dictionary) -> void:
@@ -7,7 +7,7 @@ extends RefCounted
7
7
  ## wrapped in the EditorUndoRedoManager so a human can Ctrl-Z anything the assistant did.
8
8
 
9
9
  const Codec := preload("res://addons/breakpoint_mcp/variant_json.gd")
10
- const ADDON_VERSION := "1.4.0"
10
+ const ADDON_VERSION := "1.4.2"
11
11
 
12
12
  var _plugin: EditorPlugin
13
13
 
@@ -3,5 +3,5 @@
3
3
  name="Breakpoint MCP"
4
4
  description="Exposes the live Godot editor and the running game to an MCP host so an AI assistant can drive them: scene/node/resource CRUD with full undo/redo, project settings, ClassDB, editor + in-game screenshots, and an in-game runtime bridge (live SceneTree, property get/set, method calls, signals, input injection, Performance monitors). Pairs with the breakpoint-mcp MCP host."
5
5
  author="James Livingston"
6
- version="1.4.0"
6
+ version="1.4.2"
7
7
  script="plugin.gd"
package/dist/index.js CHANGED
@@ -17,9 +17,11 @@ import { registerCsDapTools } from "./tools/csdap.js";
17
17
  import { registerRuntimeTools } from "./tools/runtime.js";
18
18
  import { registerProcessTools } from "./tools/processes.js";
19
19
  import { registerKnowledgeTools } from "./tools/knowledge.js";
20
+ import { registerVcsTools } from "./tools/vcs.js";
20
21
  import { registerAssetGenTools } from "./tools/assetgen.js";
21
22
  import { registerNetcodeTools } from "./tools/netcode.js";
22
23
  import { registerBackendTools } from "./tools/backend.js";
24
+ import { registerTabletopTools } from "./tools/tabletop.js";
23
25
  import { registerResources } from "./tools/resources.js";
24
26
  import { applyOutputSchemas } from "./schemas.js";
25
27
  import { taskStore, TASK_CAPABILITIES } from "./tasks.js";
@@ -43,7 +45,7 @@ async function main() {
43
45
  // so long jobs (export/import/headless script) support poll/await/cancel.
44
46
  // D3: also advertise resources.subscribe so clients can subscribe to
45
47
  // godot://… resources and receive notifications/resources/updated.
46
- const server = new McpServer({ name: "breakpoint-mcp", version: "1.4.1" }, { capabilities: { ...TASK_CAPABILITIES, ...RESOURCE_CAPABILITIES }, taskStore });
48
+ const server = new McpServer({ name: "breakpoint-mcp", version: "1.11.0" }, { capabilities: { ...TASK_CAPABILITIES, ...RESOURCE_CAPABILITIES }, taskStore });
47
49
  // B1: enforce frozen output schemas on every structured tool. Must run before
48
50
  // the register*Tools calls below — it wraps server.registerTool.
49
51
  applyOutputSchemas(server);
@@ -65,6 +67,11 @@ async function main() {
65
67
  const processes = registerProcessTools(server, config);
66
68
  // Group K: host-side knowledge & search (project grep, symbol/usage index, idiom lookup).
67
69
  registerKnowledgeTools(server, config);
70
+ // Group L: host-side version control (vcs_*). Read-only core over the `git` binary
71
+ // (status/log/diff/show/branches/blame) rooted at the project path — no editor, no
72
+ // language server, so it answers whenever the project is a git work tree. Mutating
73
+ // git tools are deferred pending a scope steer and will reuse the elicitation gate.
74
+ registerVcsTools(server, config);
68
75
  // Group J: AI asset generation (delegated backend / connected client; degrades
69
76
  // to a request spec when no backend is configured). Writes + imports via the bridge.
70
77
  registerAssetGenTools(server, bridge, config);
@@ -76,6 +83,11 @@ async function main() {
76
83
  // Detects the installed SDK (SilentWolf/Nakama/PlayFab/Photon) and generates gated
77
84
  // GDScript against it; degrades cleanly when the SDK is absent or lacks the feature.
78
85
  registerBackendTools(server, bridge, config);
86
+ // Group N: card/board/piece authoring composites (card_*). Host-side scripted
87
+ // sequences of existing editor-bridge primitives (scene/control/node/theme/
88
+ // resource) — they build + data-bind scenes, add no addon method, and invent
89
+ // no game rules or data.
90
+ registerTabletopTools(server, bridge, config);
79
91
  // Phase 4: MCP resources (scene tree, editor state, runtime tree/log, ClassDB docs).
80
92
  registerResources(server, bridge, runtime);
81
93
  // D3: resource subscriptions — push notifications/resources/updated when a
package/dist/schemas.js CHANGED
@@ -480,6 +480,95 @@ export const outputSchemas = {
480
480
  })),
481
481
  available: z.array(z.string()),
482
482
  },
483
+ // Group L — version control (tools/vcs.ts). Read-only git wrappers; shapes frozen
484
+ // from the host reshaping in vcs.ts (git status --porcelain=v2 / log / diff / show
485
+ // / branch / blame --line-porcelain).
486
+ vcs_status: {
487
+ branch: z.string().nullable(),
488
+ oid: z.string().nullable(),
489
+ upstream: z.string().nullable(),
490
+ ahead: z.number(),
491
+ behind: z.number(),
492
+ staged: z.array(z.object({ path: z.string(), status: z.string() })),
493
+ unstaged: z.array(z.object({ path: z.string(), status: z.string() })),
494
+ untracked: z.array(z.string()),
495
+ unmerged: z.array(z.string()),
496
+ clean: z.boolean(),
497
+ },
498
+ vcs_log: {
499
+ commits: z.array(z.object({
500
+ hash: z.string(), short: z.string(), author: z.string(), date: z.string(), subject: z.string(),
501
+ })),
502
+ count: z.number(),
503
+ },
504
+ vcs_diff: {
505
+ staged: z.boolean(),
506
+ path: z.string().nullable(),
507
+ files: z.array(z.string()),
508
+ patch: z.string(),
509
+ truncated: z.boolean(),
510
+ },
511
+ // vcs_show has two modes (commit vs file-at-ref); only `ref` is always present, the
512
+ // rest are populated per mode, so all but `ref` are optional.
513
+ vcs_show: {
514
+ ref: z.string(),
515
+ hash: z.string().optional(),
516
+ short: z.string().optional(),
517
+ author: z.string().optional(),
518
+ date: z.string().optional(),
519
+ subject: z.string().optional(),
520
+ body: z.string().optional(),
521
+ patch: z.string().optional(),
522
+ path: z.string().optional(),
523
+ content: z.string().optional(),
524
+ truncated: z.boolean(),
525
+ },
526
+ vcs_branch_list: {
527
+ current: z.string().nullable(),
528
+ branches: z.array(z.object({
529
+ name: z.string(), short_sha: z.string(), current: z.boolean(), remote: z.boolean(),
530
+ })),
531
+ count: z.number(),
532
+ },
533
+ vcs_blame: {
534
+ path: z.string(),
535
+ lines: z.array(z.object({
536
+ line: z.number(), commit: z.string(), author: z.string(), date: z.string(), text: z.string(),
537
+ })),
538
+ count: z.number(),
539
+ truncated: z.boolean(),
540
+ },
541
+ // Group L mutating tools (Tier A). Success shapes only; gated tools that are
542
+ // blocked/declined return isError (exempt from output-schema validation).
543
+ vcs_add: {
544
+ staged: z.array(z.object({ path: z.string(), status: z.string() })),
545
+ count: z.number(),
546
+ },
547
+ vcs_commit: {
548
+ committed: z.boolean(),
549
+ hash: z.string(),
550
+ short: z.string(),
551
+ summary: z.string(),
552
+ },
553
+ vcs_restore: {
554
+ restored: z.array(z.string()),
555
+ count: z.number(),
556
+ },
557
+ vcs_stash: {
558
+ op: z.string(),
559
+ message: z.string(),
560
+ stashes: z.array(z.object({ ref: z.string(), description: z.string() })),
561
+ },
562
+ vcs_branch_create: {
563
+ created: z.boolean(),
564
+ name: z.string(),
565
+ from: z.string().nullable(),
566
+ switched: z.boolean(),
567
+ },
568
+ vcs_switch: {
569
+ switched: z.boolean(),
570
+ branch: z.string(),
571
+ },
483
572
  // ClassDB-backed reference tools (tools/editor.ts -> operations.gd _classdb_reference / _docs_search).
484
573
  class_reference: {
485
574
  class: z.string(),
@@ -582,6 +671,132 @@ export const outputSchemas = {
582
671
  auth_scaffold: backendScaffold,
583
672
  };
584
673
  })(),
674
+ // ---- Group N: card/board/piece authoring composites (tools/tabletop.ts) ----
675
+ card_template_create: {
676
+ scene_path: z.string(),
677
+ script_path: z.string(),
678
+ root_type: z.string(),
679
+ has_back: z.boolean(),
680
+ node_count: z.number(),
681
+ saved: z.boolean(),
682
+ slots: z.array(z.object({ name: z.string(), node_path: z.string(), kind: z.string() })),
683
+ },
684
+ card_instance: {
685
+ instance_path: z.string(),
686
+ face_up: z.boolean(),
687
+ bound: z.array(z.string()),
688
+ unbound: z.array(z.string()),
689
+ },
690
+ card_hand_layout: {
691
+ container_path: z.string(),
692
+ mode: z.string(),
693
+ count: z.number(),
694
+ instances: z.array(z.object({ index: z.number(), instance_path: z.string() })),
695
+ },
696
+ card_deck_from_table: {
697
+ deck_container: z.string(),
698
+ count: z.number(),
699
+ rows_read: z.number(),
700
+ rows_skipped: z.number(),
701
+ unmapped_columns: z.array(z.string()),
702
+ instances: z.array(z.object({ row_index: z.number(), instance_path: z.string() })),
703
+ },
704
+ card_set_face: {
705
+ node_path: z.string(),
706
+ face_up: z.boolean(),
707
+ method: z.string(),
708
+ animated: z.boolean(),
709
+ player_path: z.string().nullable(),
710
+ anim: z.string().nullable(),
711
+ },
712
+ board_create: {
713
+ scene_path: z.string(),
714
+ root_type: z.string(),
715
+ cell_kind: z.string(),
716
+ layout_mode: z.string(),
717
+ cell_count: z.number(),
718
+ node_count: z.number(),
719
+ saved: z.boolean(),
720
+ cells: z.array(z.object({ id: z.string(), node_path: z.string(), x: z.number(), y: z.number() })),
721
+ },
722
+ board_place: {
723
+ placed: z.boolean(),
724
+ cell: z.string(),
725
+ cell_path: z.string(),
726
+ node_path: z.string(),
727
+ align: z.object({ x: z.number(), y: z.number() }),
728
+ },
729
+ board_tile_create: {
730
+ scene_path: z.string(),
731
+ layer_path: z.string(),
732
+ layer_name: z.string(),
733
+ rows: z.number(),
734
+ cols: z.number(),
735
+ tile_size: z.array(z.number()),
736
+ tileset_path: z.string(),
737
+ tileset_created: z.boolean(),
738
+ cell_count: z.number(),
739
+ painted: z.boolean(),
740
+ node_count: z.number(),
741
+ saved: z.boolean(),
742
+ },
743
+ board_tile_place: {
744
+ placed: z.boolean(),
745
+ coord: z.array(z.number()),
746
+ layer_path: z.string(),
747
+ node_path: z.string(),
748
+ local_pos: z.object({ x: z.number(), y: z.number() }),
749
+ tile_size: z.array(z.number()),
750
+ anchor: z.string(),
751
+ align: z.object({ x: z.number(), y: z.number() }),
752
+ reparented: z.boolean(),
753
+ },
754
+ piece_template_create: {
755
+ scene_path: z.string(),
756
+ script_path: z.string(),
757
+ root_type: z.string(),
758
+ has_label: z.boolean(),
759
+ has_hit_area: z.boolean(),
760
+ has_back: z.boolean(),
761
+ node_count: z.number(),
762
+ saved: z.boolean(),
763
+ nodes: z.array(z.object({ name: z.string(), node_path: z.string(), type: z.string() })),
764
+ },
765
+ piece_instance: {
766
+ instance_path: z.string(),
767
+ face_up: z.boolean(),
768
+ bound: z.array(z.string()),
769
+ unbound: z.array(z.string()),
770
+ placed: z.boolean(),
771
+ cell: z.string().nullable(),
772
+ },
773
+ piece_move: {
774
+ moved: z.boolean(),
775
+ from: z.string().nullable(),
776
+ to: z.string(),
777
+ node_path: z.string(),
778
+ animated: z.boolean(),
779
+ },
780
+ interact_make_draggable: {
781
+ node_path: z.string(),
782
+ mode: z.string(),
783
+ script_path: z.string(),
784
+ payload_keys: z.array(z.string()),
785
+ action: z.string().nullable(),
786
+ connected: z.boolean(),
787
+ composed: z.boolean(),
788
+ base_script: z.string().nullable(),
789
+ },
790
+ interact_add_drop_zone: {
791
+ node_path: z.string(),
792
+ mode: z.string(),
793
+ script_path: z.string(),
794
+ on_drop: z.string(),
795
+ accepts_key: z.string(),
796
+ accepts_values: z.array(z.string()),
797
+ notified: z.boolean(),
798
+ area_path: z.string().nullable(),
799
+ },
585
800
  };
586
801
  /**
587
802
  * Inject the frozen output schemas into every matching tool at registration