breakpoint-mcp 1.74.0 โ†’ 1.74.1

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/dist/bridge.js CHANGED
@@ -39,7 +39,17 @@ export class BridgeClient {
39
39
  eventListeners = new Set();
40
40
  wantConnected = false;
41
41
  reconnectTimer = null;
42
- constructor(host, port, defaultTimeoutMs, label = "editor bridge", hint = 'Is the editor open with the "Breakpoint MCP" plugin enabled?',
42
+ constructor(host, port, defaultTimeoutMs, label = "editor bridge",
43
+ // ๐Ÿ”ด THE SECOND SENTENCE IS THE ONE THAT EARNS ITS PLACE. The first asks a
44
+ // question whose answer, for the user most likely to be reading this line,
45
+ // is YES: the editor IS open and the plugin IS listed in project.godot.
46
+ // Godot reads `[editor_plugins]` at project load and never re-reads it, so
47
+ // an editor that was already running when `init` wrote the section keeps
48
+ // the plugin disabled โ€” the bridge never starts, every editor-plane tool is
49
+ // dark, and the remedy agrees with everything the user can see. `init` now
50
+ // warns at the point it writes; this is the same fact told to the person
51
+ // who did not read that line, or who read it a week ago.
52
+ hint = 'Is the editor open with the "Breakpoint MCP" plugin enabled? If it was ALREADY open when you ran `breakpoint-mcp init`, close and reopen the project โ€” Godot reads the enabled-plugin list only at project load.',
43
53
  /**
44
54
  * Loopback-auth secret provider, read lazily on each connect. When it
45
55
  * returns a non-empty string the client sends it as the FIRST line on the
package/dist/cli/args.js CHANGED
@@ -1,4 +1,4 @@
1
- export function parseArgs(argv, booleanFlags = []) {
1
+ export function parseArgs(argv, booleanFlags = [], knownFlags) {
2
2
  const bool = new Set(booleanFlags);
3
3
  const positionals = [];
4
4
  const flags = {};
@@ -35,6 +35,36 @@ export function parseArgs(argv, booleanFlags = []) {
35
35
  }
36
36
  positionals.push(a);
37
37
  }
38
- return { positionals, flags };
38
+ // Computed AFTER the loop rather than inside it, so `--flag=value`,
39
+ // `--flag value` and the bare boolean forms are all judged by the same rule โ€”
40
+ // the key that landed in `flags`, however it got there.
41
+ const known = knownFlags === undefined ? null : new Set([...knownFlags, ...booleanFlags]);
42
+ const unknown = known === null ? [] : Object.keys(flags).filter((k) => !known.has(k));
43
+ return { positionals, flags, unknown };
44
+ }
45
+ /**
46
+ * The two questions every subcommand must answer before it does any work: was
47
+ * `--help` asked for, and was it handed something it does not accept. Returns
48
+ * the exit code to return, or `null` to carry on.
49
+ *
50
+ * Shared rather than written per subcommand, because written per subcommand it
51
+ * was answered by exactly ONE of the three. `tools` handled `--help` and kept
52
+ * its own copy of the text; `doctor` ignored it and ran the checks; `init`
53
+ * ignored it and fell through to the project check, reporting a missing
54
+ * `project.godot` โ€” an error about the user's directory in answer to a
55
+ * question about flags.
56
+ */
57
+ export function preflight(parsed, subcommand, usage) {
58
+ if (parsed.flags.help === true || parsed.flags.h === true) {
59
+ process.stdout.write(usage.join("\n") + "\n");
60
+ return 0;
61
+ }
62
+ if (parsed.unknown.length > 0) {
63
+ const named = parsed.unknown.map((u) => (u.length === 1 ? `-${u}` : `--${u}`)).join(", ");
64
+ process.stderr.write(`${subcommand}: unknown ${parsed.unknown.length === 1 ? "option" : "options"} ${named}.\n` +
65
+ ` Run \`breakpoint-mcp ${subcommand} --help\` for the options this subcommand accepts.\n`);
66
+ return 2;
67
+ }
68
+ return null;
39
69
  }
40
70
  //# sourceMappingURL=args.js.map
@@ -23,7 +23,8 @@ import path from "node:path";
23
23
  import { spawnSync } from "node:child_process";
24
24
  import { loadConfig } from "../config.js";
25
25
  import { CAPABILITY_GROUPS, droppedTools, selectPrivilegedGroups } from "../capabilities.js";
26
- import { parseArgs } from "./args.js";
26
+ import { parseArgs, preflight } from "./args.js";
27
+ import { DOCTOR_FLAGS, DOCTOR_USAGE } from "./usage.js";
27
28
  import { BridgeClient } from "../bridge.js";
28
29
  import { resolveBridgeSecret } from "../secret.js";
29
30
  const ADDON_REL = "addons/breakpoint_mcp";
@@ -257,7 +258,7 @@ export async function runDoctorChecks(config, opts) {
257
258
  name: "editor-bridge",
258
259
  host: config.bridgeHost,
259
260
  port: config.bridgePort,
260
- hint: 'Open the editor with the "Breakpoint MCP" plugin enabled.',
261
+ hint: 'Open the editor with the "Breakpoint MCP" plugin enabled โ€” and if it was already open when you ran `breakpoint-mcp init`, close and reopen the project (Godot reads the enabled-plugin list only at project load).',
261
262
  secretEnv: ["BREAKPOINT_BRIDGE_SECRET"],
262
263
  },
263
264
  {
@@ -344,7 +345,11 @@ function printReport(report) {
344
345
  }
345
346
  /** Entry point for `breakpoint-mcp doctor`. Returns the process exit code. */
346
347
  export async function runDoctor(argv) {
347
- const { flags } = parseArgs(argv, ["json", "require-live", "include-csharp"]);
348
+ const parsed = parseArgs(argv, ["json", "require-live", "include-csharp", "help", "h"], DOCTOR_FLAGS);
349
+ const pre = preflight(parsed, "doctor", DOCTOR_USAGE);
350
+ if (pre !== null)
351
+ return pre;
352
+ const { flags } = parsed;
348
353
  if (typeof flags.project === "string")
349
354
  process.env.GODOT_PROJECT = flags.project;
350
355
  const timeoutRaw = typeof flags.timeout === "string" ? Number.parseInt(flags.timeout, 10) : NaN;
package/dist/cli/init.js CHANGED
@@ -12,7 +12,8 @@ import fs from "node:fs";
12
12
  import os from "node:os";
13
13
  import path from "node:path";
14
14
  import { fileURLToPath } from "node:url";
15
- import { parseArgs } from "./args.js";
15
+ import { parseArgs, preflight } from "./args.js";
16
+ import { INIT_FLAGS, INIT_USAGE } from "./usage.js";
16
17
  import { CLIENT_IDS, clientInfo, mergeClientConfig, serverEntry, snippet } from "./clients.js";
17
18
  import { DEFAULT_REPO, fetchAddonFromGitHub } from "./github.js";
18
19
  import { CAPABILITY_GROUPS, PRIVILEGED_TOOL_COUNT, parsePrivilegedGroups, selectPrivilegedGroups, } from "../capabilities.js";
@@ -147,7 +148,11 @@ function claudeCodeCommand(projectPath, privilegedGroups) {
147
148
  }
148
149
  /** Entry point for `breakpoint-mcp init`. Returns the process exit code. */
149
150
  export async function runInit(argv, deps = {}) {
150
- const { flags } = parseArgs(argv, ["force", "dry-run"]);
151
+ const parsed = parseArgs(argv, ["force", "dry-run", "help", "h"], INIT_FLAGS);
152
+ const pre = preflight(parsed, "init", INIT_USAGE);
153
+ if (pre !== null)
154
+ return pre;
155
+ const { flags } = parsed;
151
156
  const projectPath = typeof flags.project === "string"
152
157
  ? path.resolve(flags.project)
153
158
  : process.env.GODOT_PROJECT ?? process.cwd();
@@ -292,7 +297,21 @@ export async function runInit(argv, deps = {}) {
292
297
  if (privilegedWarn)
293
298
  say(` note: ${privilegedWarn}`);
294
299
  say("");
295
- say("Next: open the project in Godot, then run `breakpoint-mcp doctor --require-live` to verify.");
300
+ // ๐Ÿ”ด THE REOPEN TRAP. Godot reads project.godot's [editor_plugins] at project load and
301
+ // does not reload it, so an editor that was already running when we wrote the section
302
+ // keeps the plugin DISABLED โ€” the bridge never starts and every editor-plane tool is
303
+ // dark, with nothing on screen saying why. The line is conditioned on `en.changed`
304
+ // because a project whose plugin was already enabled has nothing to reopen for, and a
305
+ // warning printed on every run is a warning nobody reads.
306
+ if (!dryRun && en.changed) {
307
+ say("Next: open the project in Godot โ€” and if it is ALREADY OPEN, close and reopen it.");
308
+ say(" Godot reads project.godot's [editor_plugins] at project load and does not reload");
309
+ say(" it, so an editor that was already running keeps the plugin disabled.");
310
+ say("Then run `breakpoint-mcp doctor --require-live` to verify.");
311
+ }
312
+ else {
313
+ say("Next: open the project in Godot, then run `breakpoint-mcp doctor --require-live` to verify.");
314
+ }
296
315
  process.stdout.write(out.join("\n") + "\n");
297
316
  return 0;
298
317
  }
package/dist/cli/tools.js CHANGED
@@ -22,7 +22,8 @@
22
22
  * `--surface` picks which one populates `tools[]`; both counts are always in the
23
23
  * header so a consumer cannot report one as the other by accident.
24
24
  */
25
- import { parseArgs } from "./args.js";
25
+ import { parseArgs, preflight } from "./args.js";
26
+ import { TOOLS_FLAGS, TOOLS_USAGE } from "./usage.js";
26
27
  import { buildToolsets } from "../toolsets.js";
27
28
  import { applyOutputSchemas } from "../schemas.js";
28
29
  import { applyAnnotations, annotationsFor } from "../annotations.js";
@@ -148,20 +149,16 @@ function renderText(report) {
148
149
  return lines.join("\n");
149
150
  }
150
151
  export async function runTools(argv) {
151
- const { flags } = parseArgs(argv, ["json", "help", "h"]);
152
- if (flags.help || flags.h) {
153
- process.stdout.write([
154
- "breakpoint-mcp tools โ€” export the tool surface (name, risk annotations, capability group).",
155
- "",
156
- " --json Emit JSON (stable, no timestamp โ€” safe to diff between releases).",
157
- " --surface <which> full | secure-default (default: secure-default, what an",
158
- " untouched install actually advertises).",
159
- "",
160
- "Reads no Godot install and starts no server โ€” the registry is built statically.",
161
- "",
162
- ].join("\n"));
163
- return 0;
164
- }
152
+ const parsed = parseArgs(argv, ["json", "help", "h"], TOOLS_FLAGS);
153
+ const pre = preflight(parsed, "tools", [
154
+ ...TOOLS_USAGE,
155
+ "",
156
+ "Reads no Godot install and starts no server โ€” the registry is built statically.",
157
+ "",
158
+ ]);
159
+ if (pre !== null)
160
+ return pre;
161
+ const { flags } = parsed;
165
162
  const raw = typeof flags.surface === "string" ? flags.surface : "secure-default";
166
163
  if (raw !== "full" && raw !== "secure-default") {
167
164
  process.stderr.write(`unknown --surface "${raw}" (expected: full | secure-default)\n`);
@@ -0,0 +1,102 @@
1
+ /**
2
+ * The CLI's help text, in ONE place, split by subcommand.
3
+ *
4
+ * It lived in `index.ts`'s `printUsage` as a single flat array, which was fine
5
+ * while `--help` was the only thing that printed it. It stopped being fine the
6
+ * moment `breakpoint-mcp init --help` had to print the init section and nothing
7
+ * else: the alternative to this module is the same twelve lines written twice,
8
+ * and a help text that disagrees with itself is worse than one that is merely
9
+ * long. `index.ts` composes the whole document from these parts, so a flag
10
+ * documented for a subcommand is documented in the top-level help by
11
+ * construction.
12
+ *
13
+ * ๐Ÿ”ด THE ROSTERS BELOW ARE NOT DOCUMENTATION โ€” THEY ARE THE PARSER'S INPUT.
14
+ * `parseArgs` takes them as `knownFlags` and reports anything else as unknown,
15
+ * so a flag added to a subcommand and not added here is refused at the command
16
+ * line rather than silently ignored. That is the whole point of keeping the
17
+ * prose and the roster in the same file: the pressure to update both is the
18
+ * same edit.
19
+ */
20
+ export const HEADLINE = "breakpoint-mcp โ€” MCP server exposing Godot to AI coding assistants.";
21
+ export const SYNOPSIS = [
22
+ "Usage:",
23
+ " breakpoint-mcp Start the MCP server on stdio (default; how MCP clients launch it).",
24
+ " breakpoint-mcp init Install + enable the editor addon in a project and wire the MCP client.",
25
+ " breakpoint-mcp doctor Check the Godot binary, the editor addon, and the four bridges.",
26
+ " breakpoint-mcp tools Export the tool surface + risk annotations (for catalogs / reviews).",
27
+ " breakpoint-mcp --help Show this help.",
28
+ " breakpoint-mcp --version Print the installed version and exit.",
29
+ "",
30
+ "Every subcommand also accepts --help.",
31
+ ];
32
+ export const INIT_USAGE = [
33
+ "Usage: breakpoint-mcp init [options]",
34
+ "",
35
+ "Install + enable the editor addon in a Godot project and wire the MCP client.",
36
+ "",
37
+ "init options:",
38
+ " --project <dir> Target Godot project (default: $GODOT_PROJECT or the current directory).",
39
+ " --client <id> Write the MCP config for a client: claude-code | claude-desktop | cursor | windsurf | vscode.",
40
+ " --force Overwrite an addon that is already installed.",
41
+ " --dry-run Print what would change without writing anything.",
42
+ " --from-github [ref] Fetch the editor addon from GitHub at [ref] (default: this package's version tag) instead of the bundled copy.",
43
+ " --repo <owner/repo> With --from-github, the source repo (default: jlivingston-Cipher/godot-breakpoint-mcp).",
44
+ " --trust <level> secure | full. `full` enables every higher-trust group (default: secure).",
45
+ " --privileged-groups <a,b> Enable named higher-trust groups instead of all of them.",
46
+ ];
47
+ export const DOCTOR_USAGE = [
48
+ "Usage: breakpoint-mcp doctor [options]",
49
+ "",
50
+ "Check the Godot binary, the editor addon, and the four bridges.",
51
+ "",
52
+ "doctor options:",
53
+ " --project <dir> Project to check (default: $GODOT_PROJECT or the current directory).",
54
+ " --require-live Also require the editor/runtime/LSP/DAP bridges to be reachable.",
55
+ " --include-csharp Also probe OmniSharp / netcoredbg on PATH (the C# planes).",
56
+ " --timeout <ms> Per-bridge connect timeout (default 1500).",
57
+ " --json Emit the report as JSON.",
58
+ ];
59
+ export const TOOLS_USAGE = [
60
+ "Usage: breakpoint-mcp tools [options]",
61
+ "",
62
+ "Export the tool surface + risk annotations (for catalogs / reviews).",
63
+ "",
64
+ "tools options:",
65
+ " --surface <which> full | secure-default (default: secure-default, what an untouched install advertises).",
66
+ " --json Emit the surface as JSON โ€” stable and timestamp-free, so releases can be diffed.",
67
+ ];
68
+ /** Every flag `init` accepts. Booleans are declared at the call site. */
69
+ export const INIT_FLAGS = [
70
+ "project",
71
+ "client",
72
+ "from-github",
73
+ "repo",
74
+ "trust",
75
+ "privileged-groups",
76
+ ];
77
+ /** Every flag `doctor` accepts. */
78
+ export const DOCTOR_FLAGS = ["project", "timeout"];
79
+ /** Every flag `tools` accepts. */
80
+ export const TOOLS_FLAGS = ["surface"];
81
+ /** The whole document, composed โ€” what `breakpoint-mcp --help` prints. */
82
+ export function fullUsage() {
83
+ // Each subcommand block contributes only its own `<name> options:` tail; the
84
+ // per-subcommand synopsis above it would read as four competing headlines in
85
+ // a single document.
86
+ const tail = (block) => block.slice(block.findIndex((l) => l.endsWith("options:")));
87
+ return [
88
+ HEADLINE,
89
+ "",
90
+ ...SYNOPSIS,
91
+ "",
92
+ ...tail(INIT_USAGE),
93
+ "",
94
+ ...tail(DOCTOR_USAGE),
95
+ "",
96
+ ...tail(TOOLS_USAGE),
97
+ "",
98
+ "All runtime configuration is via environment variables; see the README.",
99
+ "",
100
+ ];
101
+ }
102
+ //# sourceMappingURL=usage.js.map
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import { CsLspClient } from "./cslsp.js";
9
9
  import { CsDapClient } from "./csdap.js";
10
10
  import { StdioChannel } from "./stdio.js";
11
11
  import { DapClient } from "./dap.js";
12
+ import { fullUsage } from "./cli/usage.js";
12
13
  import { buildToolsets } from "./toolsets.js";
13
14
  import { registerRecipes } from "./recipes.js";
14
15
  import { applyWireDefaults } from "./wire-defaults.js";
@@ -42,7 +43,7 @@ async function main() {
42
43
  // so long jobs (export/import/headless script) support poll/await/cancel.
43
44
  // D3: also advertise resources.subscribe so clients can subscribe to
44
45
  // godot://โ€ฆ resources and receive notifications/resources/updated.
45
- const server = new McpServer({ name: "breakpoint-mcp", version: "1.74.0" }, { capabilities: { ...TASK_CAPABILITIES, ...RESOURCE_CAPABILITIES }, taskStore });
46
+ const server = new McpServer({ name: "breakpoint-mcp", version: "1.74.1" }, { capabilities: { ...TASK_CAPABILITIES, ...RESOURCE_CAPABILITIES }, taskStore });
46
47
  // 208 ยง7.1 โ€” delete from the wire what the protocol already says: the SDK's
47
48
  // draft-07 `$schema` inside every input/output schema (MCP's default dialect is
48
49
  // 2020-12, and declaring another is an explicit switch a peer may reject), and
@@ -160,44 +161,26 @@ async function main() {
160
161
  process.on("SIGTERM", shutdown);
161
162
  }
162
163
  function printUsage() {
163
- process.stdout.write([
164
- "breakpoint-mcp โ€” MCP server exposing Godot to AI coding assistants.",
165
- "",
166
- "Usage:",
167
- " breakpoint-mcp Start the MCP server on stdio (default; how MCP clients launch it).",
168
- " breakpoint-mcp init Install + enable the editor addon in a project and wire the MCP client.",
169
- " breakpoint-mcp doctor Check the Godot binary, the editor addon, and the four bridges.",
170
- " breakpoint-mcp tools Export the tool surface + risk annotations (for catalogs / reviews).",
171
- " breakpoint-mcp --help Show this help.",
172
- "",
173
- "init options:",
174
- " --project <dir> Target Godot project (default: $GODOT_PROJECT or the current directory).",
175
- " --client <id> Write the MCP config for a client: claude-code | claude-desktop | cursor | windsurf | vscode.",
176
- " --force Overwrite an addon that is already installed.",
177
- " --dry-run Print what would change without writing anything.",
178
- " --from-github [ref] Fetch the editor addon from GitHub at [ref] (default: this package's version tag) instead of the bundled copy.",
179
- " --repo <owner/repo> With --from-github, the source repo (default: jlivingston-Cipher/godot-breakpoint-mcp).",
180
- " --trust <level> secure | full. `full` enables every higher-trust group (default: secure).",
181
- " --privileged-groups <a,b> Enable named higher-trust groups instead of all of them.",
182
- "",
183
- "doctor options:",
184
- " --project <dir> Project to check (default: $GODOT_PROJECT or the current directory).",
185
- " --require-live Also require the editor/runtime/LSP/DAP bridges to be reachable.",
186
- " --include-csharp Also probe OmniSharp / netcoredbg on PATH (the C# planes).",
187
- " --timeout <ms> Per-bridge connect timeout (default 1500).",
188
- " --json Emit the report as JSON.",
189
- "",
190
- "tools options:",
191
- " --surface <which> full | secure-default (default: secure-default, what an untouched install advertises).",
192
- " --json Emit the surface as JSON โ€” stable and timestamp-free, so releases can be diffed.",
193
- "",
194
- "All runtime configuration is via environment variables; see the README.",
195
- "",
196
- ].join("\n"));
164
+ process.stdout.write(fullUsage().join("\n"));
197
165
  }
198
- // Subcommand dispatch. Anything that isn't a recognized subcommand โ€” including
199
- // no arguments at all, which is how every MCP client launches this โ€” falls
200
- // through to the stdio server, so the server's launch contract is unchanged.
166
+ // Subcommand dispatch. NO arguments at all is how every MCP client launches
167
+ // this โ€” every invocation in the README, the user guide and `init`'s own
168
+ // emitted config passes the binary an empty argv โ€” so that case, and only that
169
+ // case, starts the stdio server. The launch contract is unchanged.
170
+ //
171
+ // ๐Ÿ”ด WHAT CHANGED IS THE FALL-THROUGH, WHICH USED TO SWALLOW EVERYTHING ELSE.
172
+ // `breakpoint-mcp --version` started a server. So did `-v`, `version`, and any
173
+ // mistyped flag: the process printed one ready line and then sat on a stdin
174
+ // nobody was going to speak MCP into, which in a terminal is a hang. The two
175
+ // halves of that are separate defects and both are user-facing:
176
+ //
177
+ // * There was NO WAY TO ASK THIS BINARY ITS VERSION. `--version` is the most
178
+ // universal convention a CLI has, and the first thing anyone filing a bug
179
+ // report is asked for. A project with 0 issues against ~3,700 downloads
180
+ // cannot afford a version query that hangs the terminal instead.
181
+ // * An unrecognized token is a TYPO, and the only useful thing to do with one
182
+ // is name it. Falling through spends the user's next twenty minutes on the
183
+ // wrong question.
201
184
  void (async () => {
202
185
  const sub = process.argv[2];
203
186
  if (sub === "doctor") {
@@ -216,6 +199,20 @@ void (async () => {
216
199
  printUsage();
217
200
  process.exit(0);
218
201
  }
202
+ if (sub === "version" || sub === "--version" || sub === "-v" || sub === "-V") {
203
+ // Bare, so `breakpoint-mcp --version` is usable in a shell substitution.
204
+ // Read from package.json rather than written here: see version.ts on the
205
+ // twenty releases that announced 0.2.0 to every LSP server they met.
206
+ const { packageVersion } = await import("./version.js");
207
+ process.stdout.write(`${packageVersion()}\n`);
208
+ process.exit(0);
209
+ }
210
+ if (sub !== undefined) {
211
+ process.stderr.write(`breakpoint-mcp: unknown argument '${sub}'.\n` +
212
+ " Run `breakpoint-mcp --help` for the subcommands and their options.\n" +
213
+ " To start the MCP server, run it with no arguments โ€” that is how MCP clients launch it.\n");
214
+ process.exit(2);
215
+ }
219
216
  await main();
220
217
  })().catch((err) => {
221
218
  log("fatal:", err instanceof Error ? err.stack ?? err.message : String(err));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "breakpoint-mcp",
3
- "version": "1.74.0",
3
+ "version": "1.74.1",
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",