breakpoint-mcp 1.2.0 → 1.3.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 +27 -8
- package/addon/breakpoint_mcp/plugin.cfg +1 -1
- package/dist/index.js +1 -1
- package/dist/schemas.js +16 -0
- package/dist/tools/lsp.js +113 -0
- 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. **244 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).
|
|
@@ -15,10 +15,28 @@ architecture.
|
|
|
15
15
|
|
|
16
16
|
## Install
|
|
17
17
|
|
|
18
|
+
The fastest path — from your Godot project folder, one command installs and enables the
|
|
19
|
+
editor addon (bundled in this package) and prints your MCP-client config:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx breakpoint-mcp init
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then open the project in Godot and verify the setup:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx breakpoint-mcp doctor
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`init` copies the addon into `addons/breakpoint_mcp/`, enables it in `project.godot`, and
|
|
32
|
+
prints — or, with `--client claude-code|claude-desktop|cursor|windsurf|vscode`, writes —
|
|
33
|
+
the client config. `doctor` checks the Godot binary, the addon, and the four bridges
|
|
34
|
+
(add `--require-live` once the editor is open; `--json` for a machine-readable report).
|
|
35
|
+
|
|
36
|
+
To install the `breakpoint-mcp` command globally instead of invoking it via `npx`:
|
|
37
|
+
|
|
18
38
|
```bash
|
|
19
|
-
|
|
20
|
-
# or
|
|
21
|
-
npm i -g breakpoint-mcp # install the `breakpoint-mcp` command
|
|
39
|
+
npm i -g breakpoint-mcp
|
|
22
40
|
```
|
|
23
41
|
|
|
24
42
|
Requires **Node ≥ 18**. The host targets the `@modelcontextprotocol/sdk` `1.x` line
|
|
@@ -60,10 +78,11 @@ section for each client's config file and format.
|
|
|
60
78
|
|
|
61
79
|
## The addon (required for the editor / runtime planes)
|
|
62
80
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
81
|
+
`breakpoint-mcp init` installs and enables this for you — the editor addon ships **inside
|
|
82
|
+
this package**, so you don't need the repository to get it. To do it by hand instead, copy
|
|
83
|
+
`addons/breakpoint_mcp/` into your project and enable it under Project Settings → Plugins.
|
|
84
|
+
Either way it opens the loopback servers this host connects to and auto-registers the
|
|
85
|
+
in-game runtime bridge; without it, only the headless-CLI (`godot_*`) plane works.
|
|
67
86
|
|
|
68
87
|
## Local-first
|
|
69
88
|
|
|
@@ -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.
|
|
6
|
+
version="1.3.0"
|
|
7
7
|
script="plugin.gd"
|
package/dist/index.js
CHANGED
|
@@ -43,7 +43,7 @@ async function main() {
|
|
|
43
43
|
// so long jobs (export/import/headless script) support poll/await/cancel.
|
|
44
44
|
// D3: also advertise resources.subscribe so clients can subscribe to
|
|
45
45
|
// godot://… resources and receive notifications/resources/updated.
|
|
46
|
-
const server = new McpServer({ name: "breakpoint-mcp", version: "1.
|
|
46
|
+
const server = new McpServer({ name: "breakpoint-mcp", version: "1.3.0" }, { capabilities: { ...TASK_CAPABILITIES, ...RESOURCE_CAPABILITIES }, taskStore });
|
|
47
47
|
// B1: enforce frozen output schemas on every structured tool. Must run before
|
|
48
48
|
// the register*Tools calls below — it wraps server.registerTool.
|
|
49
49
|
applyOutputSchemas(server);
|
package/dist/schemas.js
CHANGED
|
@@ -326,6 +326,22 @@ export const outputSchemas = {
|
|
|
326
326
|
red: z.number(), green: z.number(), blue: z.number(), alpha: z.number(), hex: z.string(),
|
|
327
327
|
})),
|
|
328
328
|
},
|
|
329
|
+
gd_call_hierarchy: {
|
|
330
|
+
direction: z.string(),
|
|
331
|
+
items: z.array(z.object({
|
|
332
|
+
name: z.string(), kind: z.string(), uri: z.string(), line: z.number(), character: z.number(), detail: z.string(),
|
|
333
|
+
calls: z.array(z.object({
|
|
334
|
+
name: z.string(), kind: z.string(), uri: z.string(), line: z.number(), character: z.number(), detail: z.string(),
|
|
335
|
+
ranges: z.array(z.object({ line: z.number(), character: z.number(), end_line: z.number(), end_character: z.number() })),
|
|
336
|
+
})),
|
|
337
|
+
})),
|
|
338
|
+
},
|
|
339
|
+
gd_semantic_tokens: {
|
|
340
|
+
token_count: z.number(),
|
|
341
|
+
tokens: z.array(z.object({
|
|
342
|
+
line: z.number(), character: z.number(), length: z.number(), type: z.string(), modifiers: z.array(z.string()),
|
|
343
|
+
})),
|
|
344
|
+
},
|
|
329
345
|
// ---- Plane D: C# semantic / OmniSharp LSP (tools/cslsp.ts) ----
|
|
330
346
|
cs_completion: { items: z.array(z.object({ label: z.string(), kind: z.string(), detail: z.string(), insertText: z.string() })) },
|
|
331
347
|
cs_hover: { contents: z.string() },
|
package/dist/tools/lsp.js
CHANGED
|
@@ -117,6 +117,52 @@ function normalizeColors(result) {
|
|
|
117
117
|
};
|
|
118
118
|
});
|
|
119
119
|
}
|
|
120
|
+
// textDocument/prepareCallHierarchy + callHierarchy/{incoming,outgoing}Calls.
|
|
121
|
+
// A CallHierarchyItem carries a SymbolKind and a selectionRange (the name range);
|
|
122
|
+
// surface the selectionRange start (falling back to the full range) so a caller
|
|
123
|
+
// gets the caret position of each symbol.
|
|
124
|
+
function normalizeCallItem(item) {
|
|
125
|
+
const it = (item ?? {});
|
|
126
|
+
const r = it.selectionRange ?? it.range ?? {};
|
|
127
|
+
return {
|
|
128
|
+
name: it.name ?? "",
|
|
129
|
+
kind: it.kind ? SYMBOL_KIND[it.kind] ?? String(it.kind) : "",
|
|
130
|
+
uri: it.uri ?? "",
|
|
131
|
+
line: r.start?.line ?? 0,
|
|
132
|
+
character: r.start?.character ?? 0,
|
|
133
|
+
detail: it.detail ?? "",
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// The call-site ranges (fromRanges) an incoming/outgoing call reports, within the
|
|
137
|
+
// file that makes the call.
|
|
138
|
+
function normalizeCallRanges(ranges) {
|
|
139
|
+
const arr = Array.isArray(ranges) ? ranges : [];
|
|
140
|
+
return arr.map((rr) => {
|
|
141
|
+
const r = (rr ?? {});
|
|
142
|
+
return {
|
|
143
|
+
line: r.start?.line ?? 0, character: r.start?.character ?? 0,
|
|
144
|
+
end_line: r.end?.line ?? 0, end_character: r.end?.character ?? 0,
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function decodeSemanticTokens(data, legend) {
|
|
149
|
+
const d = Array.isArray(data) ? data : [];
|
|
150
|
+
const types = legend?.tokenTypes ?? [];
|
|
151
|
+
const mods = legend?.tokenModifiers ?? [];
|
|
152
|
+
const out = [];
|
|
153
|
+
let line = 0, char = 0;
|
|
154
|
+
for (let i = 0; i + 4 < d.length; i += 5) {
|
|
155
|
+
const dLine = d[i] ?? 0, dChar = d[i + 1] ?? 0, len = d[i + 2] ?? 0, typeIdx = d[i + 3] ?? 0, modBits = d[i + 4] ?? 0;
|
|
156
|
+
line += dLine;
|
|
157
|
+
char = dLine === 0 ? char + dChar : dChar;
|
|
158
|
+
const modifiers = [];
|
|
159
|
+
for (let b = 0; b < 32; b++)
|
|
160
|
+
if (modBits & (1 << b))
|
|
161
|
+
modifiers.push(mods[b] ?? String(b));
|
|
162
|
+
out.push({ line, character: char, length: len, type: types[typeIdx] ?? String(typeIdx), modifiers });
|
|
163
|
+
}
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
120
166
|
// offsetOf / applyTextEdits now live in ./lsp-common.js (shared with the C#
|
|
121
167
|
// rename mutator, cs_rename). Imported above.
|
|
122
168
|
export function registerLspTools(server, lsp, cfg) {
|
|
@@ -572,5 +618,72 @@ export function registerLspTools(server, lsp, cfg) {
|
|
|
572
618
|
return fail(err);
|
|
573
619
|
}
|
|
574
620
|
});
|
|
621
|
+
// ---- Phase 2 LSP-depth: call hierarchy + semantic tokens -----------------
|
|
622
|
+
// Two heavier semantic providers. Godot's GDScript language server does not
|
|
623
|
+
// advertise callHierarchyProvider or semanticTokensProvider on current builds
|
|
624
|
+
// (observed through 4.7), so both feature-detect and return a clear
|
|
625
|
+
// "unsupported" message, keeping the D7 -32601 belt-and-suspenders in case a
|
|
626
|
+
// future build advertises one but still answers "method not found".
|
|
627
|
+
server.registerTool("gd_call_hierarchy", {
|
|
628
|
+
title: "GDScript call hierarchy",
|
|
629
|
+
description: "Find the callers (direction=incoming, the default) or callees (direction=outgoing) of the function at a position. " +
|
|
630
|
+
"Resolves the symbol with textDocument/prepareCallHierarchy, then callHierarchy/incomingCalls or outgoingCalls, and " +
|
|
631
|
+
"returns each related function with the call-site ranges. Read-only. " +
|
|
632
|
+
"Godot's GDScript language server does not advertise callHierarchyProvider through 4.7; feature-detected.",
|
|
633
|
+
inputSchema: {
|
|
634
|
+
...posSchema,
|
|
635
|
+
direction: z.enum(["incoming", "outgoing"]).optional().describe("incoming = who calls this (default); outgoing = what this calls"),
|
|
636
|
+
},
|
|
637
|
+
}, async ({ path, line, character, direction }) => {
|
|
638
|
+
const dir = direction ?? "incoming";
|
|
639
|
+
const alt = "Use gd_references to find where a symbol is used, or gd_definition to jump to it.";
|
|
640
|
+
try {
|
|
641
|
+
const caps = await lsp.getServerCapabilities();
|
|
642
|
+
if (!caps.callHierarchyProvider)
|
|
643
|
+
return unsupportedLsp("gd_call_hierarchy", "textDocument/prepareCallHierarchy", "callHierarchyProvider", alt);
|
|
644
|
+
const uri = await openAndPos(path);
|
|
645
|
+
const prepared = (await lsp.request("textDocument/prepareCallHierarchy", { textDocument: { uri }, position: { line, character } }));
|
|
646
|
+
const method = dir === "outgoing" ? "callHierarchy/outgoingCalls" : "callHierarchy/incomingCalls";
|
|
647
|
+
const items = await Promise.all((prepared ?? []).map(async (prep) => {
|
|
648
|
+
const calls = (await lsp.request(method, { item: prep }));
|
|
649
|
+
const related = (calls ?? []).map((c) => {
|
|
650
|
+
const cc = (c ?? {});
|
|
651
|
+
const peer = dir === "outgoing" ? cc.to : cc.from;
|
|
652
|
+
return { ...normalizeCallItem(peer), ranges: normalizeCallRanges(cc.fromRanges) };
|
|
653
|
+
});
|
|
654
|
+
return { ...normalizeCallItem(prep), calls: related };
|
|
655
|
+
}));
|
|
656
|
+
return ok({ direction: dir, items });
|
|
657
|
+
}
|
|
658
|
+
catch (err) {
|
|
659
|
+
if (isMethodNotFound(err))
|
|
660
|
+
return unsupportedLsp("gd_call_hierarchy", "textDocument/prepareCallHierarchy", "callHierarchyProvider", alt);
|
|
661
|
+
return fail(err);
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
server.registerTool("gd_semantic_tokens", {
|
|
665
|
+
title: "GDScript semantic tokens",
|
|
666
|
+
description: "Return the semantic-highlighting tokens for a whole script — each token's position, length, type (e.g. function, " +
|
|
667
|
+
"variable, keyword) and modifiers — decoded from the LSP packed-integer form through the server's advertised legend. " +
|
|
668
|
+
"Read-only. Godot's GDScript language server does not advertise semanticTokensProvider through 4.7; feature-detected.",
|
|
669
|
+
inputSchema: { path: z.string().describe("Script path (res://..., absolute, or project-relative)") },
|
|
670
|
+
}, async ({ path }) => {
|
|
671
|
+
const alt = "Use gd_document_symbols for a structural outline of the file.";
|
|
672
|
+
try {
|
|
673
|
+
const caps = await lsp.getServerCapabilities();
|
|
674
|
+
const provider = caps.semanticTokensProvider;
|
|
675
|
+
if (!provider)
|
|
676
|
+
return unsupportedLsp("gd_semantic_tokens", "textDocument/semanticTokens/full", "semanticTokensProvider", alt);
|
|
677
|
+
const uri = await openAndPos(path);
|
|
678
|
+
const result = (await lsp.request("textDocument/semanticTokens/full", { textDocument: { uri } }));
|
|
679
|
+
const tokens = decodeSemanticTokens(result?.data ?? [], provider.legend);
|
|
680
|
+
return ok({ token_count: tokens.length, tokens });
|
|
681
|
+
}
|
|
682
|
+
catch (err) {
|
|
683
|
+
if (isMethodNotFound(err))
|
|
684
|
+
return unsupportedLsp("gd_semantic_tokens", "textDocument/semanticTokens/full", "semanticTokensProvider", alt);
|
|
685
|
+
return fail(err);
|
|
686
|
+
}
|
|
687
|
+
});
|
|
575
688
|
}
|
|
576
689
|
//# sourceMappingURL=lsp.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "breakpoint-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.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",
|