@bridge_gpt/mcp-server 0.2.41 → 0.2.43

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.
Files changed (88) hide show
  1. package/README.md +330 -191
  2. package/build/agent-capabilities/cli.js +2 -1
  3. package/build/agent-launchers/claude-executor-adapter.js +17 -4
  4. package/build/agents.generated.js +2 -2
  5. package/build/claude-review-workflow.js +510 -45
  6. package/build/claude-user-config-doctor.js +42 -11
  7. package/build/cli-release.js +2 -1
  8. package/build/commands.generated.js +6 -5
  9. package/build/conduct-epic/bridge-client.js +354 -113
  10. package/build/conduct-epic/checkpoint-store.js +17 -0
  11. package/build/conduct-epic/cli.js +947 -99
  12. package/build/conduct-epic/cut-protocol.js +327 -0
  13. package/build/conduct-epic/spawn.js +14 -2
  14. package/build/conductor/bridge-api-client.js +148 -1
  15. package/build/conductor/cli.js +109 -1
  16. package/build/conductor/doctor.js +101 -16
  17. package/build/conductor/epic-reconcile.js +72 -19
  18. package/build/conductor/epic-runtime.js +15 -3
  19. package/build/conductor/errors.js +47 -0
  20. package/build/conductor/git-hooks.js +205 -11
  21. package/build/conductor/install-doctor.js +230 -1
  22. package/build/conductor/local-merge.js +130 -28
  23. package/build/conductor/recovery-cli.js +313 -0
  24. package/build/conductor/recovery-operations.js +219 -0
  25. package/build/conductor/tools.js +32 -3
  26. package/build/conductor/worker-ledger-cli.js +27 -1
  27. package/build/conductor-bin.js +20 -16
  28. package/build/credentials-cli.js +3 -2
  29. package/build/docs.generated.js +2 -1
  30. package/build/doctor.js +120 -44
  31. package/build/drive-epic.js +375 -0
  32. package/build/executor/cli.js +48 -1
  33. package/build/executor/env.js +21 -0
  34. package/build/executor/http-client.js +71 -3
  35. package/build/executor/index-scope.js +39 -0
  36. package/build/executor/job-errors.js +9 -0
  37. package/build/executor/job-log-registry.js +69 -0
  38. package/build/executor/job-runner.js +198 -29
  39. package/build/executor/live-worker-registry.js +83 -0
  40. package/build/executor/observation.js +259 -6
  41. package/build/executor/platform.js +147 -3
  42. package/build/executor/process.js +58 -14
  43. package/build/executor/runner.js +454 -48
  44. package/build/executor/test-clock.js +3 -2
  45. package/build/executor/worker-finalization.js +233 -56
  46. package/build/executor/worktree.js +8 -1
  47. package/build/index-scope-contract.js +96 -0
  48. package/build/index.js +2277 -270
  49. package/build/init.js +83 -22
  50. package/build/install-bridge-conductor.js +323 -14
  51. package/build/install-bridge.js +225 -47
  52. package/build/install-doctor.js +23 -9
  53. package/build/install-reexec.js +2 -1
  54. package/build/launcher-config-inspection.js +83 -22
  55. package/build/mcp-host-config.js +331 -67
  56. package/build/mcp-host-targets.js +45 -21
  57. package/build/mcp-identity.js +92 -0
  58. package/build/mcp-install-state.js +94 -1
  59. package/build/mcp-invoke.js +2 -1
  60. package/build/mcp-provisioning.js +45 -12
  61. package/build/mcp-registration-doctor.js +35 -13
  62. package/build/mcp-server-invocation.js +4 -2
  63. package/build/merge-pull-request.js +208 -9
  64. package/build/pipelines.generated.js +305 -15
  65. package/build/plane/cli.js +73 -7
  66. package/build/plane/defaults.js +18 -5
  67. package/build/plane/manifest.js +90 -0
  68. package/build/plane/preflight.js +100 -10
  69. package/build/plane/shutdown.js +71 -3
  70. package/build/plane/test-fakes.js +9 -1
  71. package/build/readme.generated.js +1 -1
  72. package/build/regression-check.js +3 -2
  73. package/build/review-tickets.js +8 -7
  74. package/build/run-unit-tests-launcher.js +149 -6
  75. package/build/schedule-run.js +3 -2
  76. package/build/setup-epic.js +531 -82
  77. package/build/sfcc/tool-wrapper.js +15 -0
  78. package/build/start-tickets-prereqs.js +11 -6
  79. package/build/start-tickets.js +91 -85
  80. package/build/update-check.js +3 -2
  81. package/build/upgrade-advice.js +2 -1
  82. package/build/upgrade-cli.js +50 -18
  83. package/build/version.generated.js +2 -1
  84. package/build/worktree-core.js +31 -17
  85. package/docs/CONDUCTOR.md +22 -0
  86. package/docs/install/mcp-tool-integrations.md +19 -3
  87. package/package.json +2 -2
  88. package/pipelines/greenfield-setup.json +286 -0
@@ -143,13 +143,15 @@ import { randomBytes as cryptoRandomBytes, createHash } from "crypto";
143
143
  import os from "os";
144
144
  import path from "path";
145
145
  import readline from "readline";
146
- import { runInit, buildBridgeApiEntry, resolveInitScaffoldAssets, refreshBridgeApiPackageSpec, currentBridgePackageSpec, } from "./init.js";
146
+ import { runInit, InitPreflightError, buildBridgeApiEntry, mergeBridgeApiProfileToken, resolveInitScaffoldAssets, refreshBridgeApiPackageSpec, currentBridgePackageSpec, } from "./init.js";
147
147
  import { hasProjectRootMarker as sharedHasProjectRootMarker } from "./project-root.js";
148
148
  import { VERSION } from "./version.generated.js";
149
149
  import { validateRepoName } from "./bridge-config.js";
150
- import { MCP_HOST_TARGETS, HOST_PLATFORM_ORDER, allHostTargets, agentForPlatform, isHostPlatformId, detectDefaultPlatforms, } from "./mcp-host-targets.js";
151
- import { provisionHostTarget, createDefaultVendorProcessDeps, } from "./mcp-host-config.js";
152
- import { recordInstalledExecutorServiceUnit, recordInstalledProjectArtifact, writeMcpInstallState, } from "./mcp-install-state.js";
150
+ import { MCP_HOST_TARGETS, HOST_PLATFORM_ORDER, allHostTargets, agentForPlatform, isHostPlatformId, detectDefaultPlatforms, hostAdapterForTarget, } from "./mcp-host-targets.js";
151
+ import { provisionHostTarget, createDefaultVendorProcessDeps, inspectBridgeApiProfileToken, } from "./mcp-host-config.js";
152
+ import { MCP_SERVER_NAME, MCP_PACKAGE_NAME, resolveRegistrationKey, } from "./mcp-identity.js";
153
+ import { DUPLICATE_REGISTRATION_GUIDANCE } from "./launcher-config-inspection.js";
154
+ import { recordInstalledConductorCapability, recordInstalledExecutorServiceUnit, recordInstalledProjectArtifact, writeMcpInstallState, } from "./mcp-install-state.js";
153
155
  // Executor provisioning (BAPI-779). The generator and the lifecycle module are
154
156
  // imported DIRECTLY and driven programmatically — never by spawning
155
157
  // `executor install-service` as a subprocess, which would lose the typed plan,
@@ -157,8 +159,10 @@ import { recordInstalledExecutorServiceUnit, recordInstalledProjectArtifact, wri
157
159
  import { collectExecutorInstallPreflight, } from "./executor/install-preflight.js";
158
160
  import { inspectExecutorServiceState, startExecutorService, } from "./executor/service-lifecycle.js";
159
161
  import { executorLaunchdLabelForId, executorSystemdUnitNameForId, executorLaunchdPlistPathForId, executorSystemdUnitPathForId, resolvePackagedExecutorInvocation, writeExecutorServicePlan, } from "./executor/service-unit.js";
162
+ import { collectConductorNativeLedgerSafe, } from "./conductor/doctor.js";
163
+ import { installConductorGitHooks, } from "./conductor/git-hooks.js";
160
164
  import { runInstallBridgeConductorCli, } from "./install-bridge-conductor.js";
161
- import { runConductorInstallDoctor, } from "./conductor/install-doctor.js";
165
+ import { runConductorInstallDoctor, CONDUCTOR_PROFILE_TOKEN, } from "./conductor/install-doctor.js";
162
166
  import { resolveConductorBridgeApiAccess, } from "./conductor/bridge-api-client.js";
163
167
  import { claudeReviewWorkflowPath, writeClaudeReviewWorkflow, } from "./claude-review-workflow.js";
164
168
  import { runSetupEpicCli } from "./setup-epic.js";
@@ -194,7 +198,7 @@ export function buildPrewarmArgs() {
194
198
  // cache with the exact tarball this `@${VERSION}`-pinned warm needs, so resolve
195
199
  // from cache and skip the redundant registry-metadata round-trip (falls back to
196
200
  // the network on a genuine miss).
197
- return ["-y", "--prefer-offline", `@bridge_gpt/mcp-server@${VERSION}`, "--version"];
201
+ return ["-y", "--prefer-offline", `${MCP_PACKAGE_NAME}@${VERSION}`, "--version"];
198
202
  }
199
203
  /** Secret-free preview of the pre-warm command (no env, no key). */
200
204
  export function buildPrewarmCommandPreview() {
@@ -202,7 +206,10 @@ export function buildPrewarmCommandPreview() {
202
206
  }
203
207
  /**
204
208
  * The natural-language prompt handed to the spawned agent session. It is
205
- * CONFIGURE-ONLY: it derives and applies configuration, presents the concise
209
+ * CONFIGURE-ONLY: it derives and applies configuration (or, on the greenfield
210
+ * path BAPI-855 added, delegates that work wholesale to the packaged
211
+ * `greenfield-setup` recipe — this prompt describes the handoff in one sentence
212
+ * and deliberately embeds no recipe orchestration of its own), presents the concise
206
213
  * capability report — the availability-framed 'Available now' / 'Needs setup'
207
214
  * pair the server authors (BAPI-719), rendered verbatim with zero local
208
215
  * curation — and closes with the server-provided `next_step.command`
@@ -216,11 +223,16 @@ export function buildPrewarmCommandPreview() {
216
223
  */
217
224
  export const INSTALL_BRIDGE_AGENT_PROMPT = "Execute the /install-bridge command in the install-spawn context (tell the command it is running " +
218
225
  "in the install-spawn context so it SKIPS its Stage 8, Stage 9, and Stage 10 offers). " +
219
- "The command chooses its own mode from the install manifest's `configured` flag do NOT force a " +
220
- "mode: when `configured` is false it runs its fresh-configuration flow, and when `configured` is " +
221
- "true it runs JOIN MODE. In JOIN MODE the command must NOT derive, approve, apply, or offer any " +
222
- "configuration and applies zero fields for any caller; a joining member's closing interaction " +
223
- "is the welcome plus the concise capability report. " +
226
+ "The command owns its own Stage 2 decision and evaluates it in a fixed order from the install " +
227
+ "manifest do NOT force an outcome. `configured == true` is JOIN MODE; otherwise a manifest whose " +
228
+ "`greenfield_setup_path` field is already set is GREENFIELD REPORT-ONLY; otherwise the command's " +
229
+ "Stage 2b classifies the repository as greenfield or established. In JOIN MODE and in GREENFIELD " +
230
+ "REPORT-ONLY the command must NOT derive, approve, apply, or offer any configuration and applies " +
231
+ "zero fields for any caller; the closing interaction is the welcome plus the concise capability " +
232
+ "report. An established classification runs the ordinary fresh-configuration flow unchanged. " +
233
+ "A greenfield classification hands off to the packaged `greenfield-setup` recipe — the command " +
234
+ "fetches it with get_pipeline_recipe and completes every returned step in THIS same session, then " +
235
+ "resumes /install-bridge reporting from the recipe's own read-after-write manifest. " +
224
236
  "Complete the command's concise capability report: the single 'What Bridge " +
225
237
  "can help with' section, rendered exactly as the server's concise_tool_capabilities field gives it. " +
226
238
  "That field is exactly two availability sections in server order — 'Available now' first, then " +
@@ -232,7 +244,7 @@ export const INSTALL_BRIDGE_AGENT_PROMPT = "Execute the /install-bridge command
232
244
  "contract metadata only — render both kinds identically, with no label, icon, or separate list, " +
233
245
  "and never branch on it. Keep an empty section visible with a short neutral line rather than " +
234
246
  "dropping it, and after both sections close with a pointer to the Bridge MCP server README at " +
235
- "https://www.npmjs.com/package/@bridge_gpt/mcp-server for the complete tool documentation, " +
247
+ `https://www.npmjs.com/package/${MCP_PACKAGE_NAME} for the complete tool documentation, ` +
236
248
  "including when either or both sections are empty. Do not render the obsolete five-section report, " +
237
249
  "and do not locally filter, count, sort, regroup, infer availability, fabricate an item, or fall " +
238
250
  "back to the complete tool_capabilities catalog or the workflows collection. If " +
@@ -255,8 +267,10 @@ export const INSTALL_BRIDGE_AGENT_PROMPT = "Execute the /install-bridge command
255
267
  "funnel with no visibility from this session, so say nothing about it at all. " +
256
268
  "Close with the server's own next step, which you must read at runtime rather than assume. Inspect " +
257
269
  "the `next_step` object on the manifest response the flow already used: on the fresh-configuration " +
258
- "path that is the Stage 7 post-apply read-after-write manifest, and in JOIN MODE it is the Stage 2 " +
259
- "manifest (JOIN MODE performs no read-after-write). Look at `next_step.command`: when it is a " +
270
+ "path that is the Stage 7 post-apply read-after-write manifest; after a greenfield handoff it is " +
271
+ "the read-after-write manifest the greenfield-setup recipe returned; and in JOIN MODE and " +
272
+ "GREENFIELD REPORT-ONLY it is the Stage 2 manifest (neither performs a read-after-write). Look at " +
273
+ "`next_step.command`: when it is a " +
260
274
  "NON-EMPTY string, recommend exactly that command, verbatim, in inline code — do not rename it or " +
261
275
  "substitute a command you happen to know. When it is an empty string, make NO next-command " +
262
276
  "recommendation at all and say nothing in its place: no heading, no placeholder, no 'nothing to " +
@@ -274,7 +288,11 @@ export const INSTALL_BRIDGE_AGENT_PROMPT = "Execute the /install-bridge command
274
288
  "the apply_install_manifest call " +
275
289
  "applied (e.g. 'Applied 8 of 9 derived fields') — if 0 fields were applied, say so loudly and " +
276
290
  "explain what is still pending. In JOIN MODE there is no apply — state instead that the project " +
277
- "was already configured and zero changes were made, without a fabricated applied count.";
291
+ "was already configured and zero changes were made, without a fabricated applied count. The two " +
292
+ "greenfield paths report the same way: GREENFIELD REPORT-ONLY states that greenfield setup was " +
293
+ "already completed and that zero changes were made, and a completed greenfield handoff states " +
294
+ "which setup path the recipe recorded — the recipe made the only apply, so neither path invents an " +
295
+ "applied count or apply buckets of its own.";
278
296
  /** Default base URL when `BAPI_BASE_URL` is unset (mirrors index.ts). */
279
297
  export const DEFAULT_BAPI_BASE_URL = "https://bridgegpt-api.com";
280
298
  /**
@@ -302,7 +320,7 @@ export function getInstallBridgeUsage(baseUrl = DEFAULT_BAPI_BASE_URL) {
302
320
  const setupUrl = buildInstallBridgeSetupUrl(baseUrl);
303
321
  return [
304
322
  "Usage:",
305
- " npx -y @bridge_gpt/mcp-server install [flags]",
323
+ ` npx -y ${MCP_PACKAGE_NAME} install [flags]`,
306
324
  "",
307
325
  "One-command Bridge API project bootstrap. Scaffolds the project, writes the",
308
326
  "per-host MCP config with your credentials, verifies connectivity, persists the",
@@ -502,8 +520,17 @@ export function getInstallBridgeUsage(baseUrl = DEFAULT_BAPI_BASE_URL) {
502
520
  " which writes the safe project-default supervisor",
503
521
  " posture in one transaction; it optionally",
504
522
  " scaffolds a parameterized claude-review workflow",
505
- " (its own separate consent), prints the capability",
506
- " matrix, and re-runs the doctor. `--dry-run`",
523
+ " (its own separate consent), then closes the local",
524
+ " out-of-the-box gap in two more phases, each behind",
525
+ " its OWN consent: tool visibility adds the",
526
+ " `conductor` token to existing .mcp.json /",
527
+ " .vscode/mcp.json / .cursor/mcp.json entries (never",
528
+ " Codex or Copilot, and never creating a file) and",
529
+ " prints the MCP-client restart advisory; local",
530
+ " observability installs the managed conductor Git",
531
+ " hooks and then reports native ledger loadability",
532
+ " read-only. It finally prints the capability",
533
+ " matrix and re-runs the doctor. `--dry-run`",
507
534
  " performs NO write of any kind and no write-consent",
508
535
  " prompt (the base-URL confirmation still applies).",
509
536
  " Executor service units are generated by",
@@ -770,7 +797,7 @@ export function promptSecretViaReadline(promptText, input = process.stdin, outpu
770
797
  * The deferral command printed with the GitHub offer (BAPI-669, U6). Declining is
771
798
  * cheap precisely because this exists — the connection is a standalone command.
772
799
  */
773
- export const INSTALL_BRIDGE_CONNECT_GITHUB_COMMAND = "npx -y @bridge_gpt/mcp-server connect-github";
800
+ export const INSTALL_BRIDGE_CONNECT_GITHUB_COMMAND = `npx -y ${MCP_PACKAGE_NAME} connect-github`;
774
801
  /**
775
802
  * The `Step 4b` label + purpose + deferral lines printed immediately before the
776
803
  * GitHub prompt (BAPI-669, U6). Shared with {@link buildLaunchStepPreview} so the
@@ -845,13 +872,13 @@ export async function offerGithubConnection(repoName, baseUrl, deps, log) {
845
872
  const code = await runGithubConnectionFlow(connectDeps, api, repoName);
846
873
  if (code !== 0) {
847
874
  log(" note: GitHub was not connected. Your install is complete — connect GitHub later with " +
848
- `'npx -y @bridge_gpt/mcp-server@latest connect-github --repo ${repoName}'.`);
875
+ `'npx -y ${MCP_PACKAGE_NAME}@latest connect-github --repo ${repoName}'.`);
849
876
  }
850
877
  }
851
878
  catch {
852
879
  // The install is already durable; a failure here is never fatal to it.
853
880
  log(" note: the GitHub connection offer could not run. Your install is complete — connect " +
854
- `GitHub later with 'npx -y @bridge_gpt/mcp-server@latest connect-github --repo ${repoName}'.`);
881
+ `GitHub later with 'npx -y ${MCP_PACKAGE_NAME}@latest connect-github --repo ${repoName}'.`);
855
882
  }
856
883
  }
857
884
  /**
@@ -995,6 +1022,12 @@ export function createDefaultInstallBridgeDeps() {
995
1022
  fetch: params.fetch,
996
1023
  reviewPolicySource: params.reviewPolicySource,
997
1024
  readWorkflowFile: () => params.readFile(claudeReviewWorkflowPath(params.cwd)),
1025
+ // BAPI-775: the SAME project root the rest of installation resolves
1026
+ // against, and the SAME read-only host-config inspector the
1027
+ // tool-visibility phase reports from — so the doctor's profile-token
1028
+ // section and the phase can never disagree about what is on disk.
1029
+ projectRoot: params.cwd,
1030
+ inspectProfileToken: (projectRoot, token) => inspectBridgeApiProfileToken(projectRoot, token, { readFile: params.readFile }),
998
1031
  // Read-only service-state probe (BAPI-779). Typed to return a state,
999
1032
  // never a runner, so the doctor cannot start/enable/reload anything.
1000
1033
  inspectExecutorServiceState: params.inspectExecutorServiceState,
@@ -1141,6 +1174,46 @@ function buildConductorExecutorDeps(deps, baseUrl) {
1141
1174
  sleep: deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
1142
1175
  };
1143
1176
  }
1177
+ /**
1178
+ * Compose the local out-of-the-box provisioning bundle the two BAPI-775 phases
1179
+ * run on, from the FINAL merged deps.
1180
+ *
1181
+ * Same shape and same rationale as {@link buildConductorExecutorDeps}: every
1182
+ * entry is a thin adapter over an existing implementation, and nothing here
1183
+ * decides anything. Consent, ordering, degradation, and reporting live in
1184
+ * `install-bridge-conductor.ts`.
1185
+ *
1186
+ * `mergeBridgeApiProfileToken` is called with the resolved project root and the
1187
+ * conductor token, reusing the helper byte-for-byte: its idempotent token-set
1188
+ * merge and its refusal to create a missing host config are load-bearing, and
1189
+ * reimplementing either here would fork the only writer of
1190
+ * `BRIDGE_MCP_PROFILE`.
1191
+ */
1192
+ function buildConductorLocalDeps(deps) {
1193
+ return {
1194
+ mergeProfileToken: () => mergeBridgeApiProfileToken(deps.cwd, CONDUCTOR_PROFILE_TOKEN),
1195
+ inspectProfileToken: () => inspectBridgeApiProfileToken(deps.cwd, CONDUCTOR_PROFILE_TOKEN, {
1196
+ readFile: deps.readFile,
1197
+ }),
1198
+ // The repository root is this command's cwd, which is what the hook
1199
+ // installer resolves its worktree and hooks directory from.
1200
+ installGitHooks: async () => installConductorGitHooks({ cwd: deps.cwd }),
1201
+ // The SHARED collector (BAPI-775), so the phase's ledger verdict and the
1202
+ // conductor doctor's cannot diverge. Its safe variant contains any
1203
+ // collection failure as a generic degraded inspection.
1204
+ probeNativeLedger: () => collectConductorNativeLedgerSafe(),
1205
+ recordCapability: async (capability) => {
1206
+ const result = await recordInstalledConductorCapability(deps.cwd, capability, {
1207
+ readFile: deps.readFile,
1208
+ writeFile: (p, data) => deps.writeFile(p, data),
1209
+ rename: deps.rename,
1210
+ mkdir: (p, o) => deps.mkdir(p, o),
1211
+ unlink: deps.unlink,
1212
+ });
1213
+ return result.ok ? { ok: true } : { ok: false, error: result.error };
1214
+ },
1215
+ };
1216
+ }
1144
1217
  /**
1145
1218
  * Read-only local service-state inspector for the unified doctor (BAPI-779).
1146
1219
  *
@@ -1928,8 +2001,8 @@ function hostConfigTargetsForPlatforms(platforms) {
1928
2001
  const set = new Set(platforms);
1929
2002
  return HOST_PLATFORM_ORDER.filter((id) => set.has(id))
1930
2003
  .map((id) => MCP_HOST_TARGETS[id])
1931
- .filter((t) => t.scope === "project" && t.format === "json")
1932
- .map((t) => ({ relPath: t.relPath, topLevelKey: t.topLevelKey }));
2004
+ .filter((t) => t.scope === "project" && hostAdapterForTarget(t).format === "json")
2005
+ .map((t) => ({ relPath: t.relPath, topLevelKey: hostAdapterForTarget(t).topLevelKey }));
1933
2006
  }
1934
2007
  /**
1935
2008
  * Derive the human-facing labels for a set of selected platforms, in registry
@@ -1942,11 +2015,13 @@ export function labelsForPlatforms(platforms) {
1942
2015
  const set = new Set(platforms);
1943
2016
  return HOST_PLATFORM_ORDER.filter((id) => set.has(id)).map((id) => MCP_HOST_TARGETS[id].label);
1944
2017
  }
2018
+ /** Project-local host-config metadata sourced from the shared registry/adapter. */
2019
+ function toHostConfigTarget(target) {
2020
+ return { relPath: target.relPath, topLevelKey: hostAdapterForTarget(target).topLevelKey };
2021
+ }
1945
2022
  /** Resolve which project-local host configs to write, mirroring runInit detection. */
1946
2023
  async function resolveHostConfigTargets(deps) {
1947
- const targets = [
1948
- { relPath: ".mcp.json", topLevelKey: "mcpServers" },
1949
- ];
2024
+ const targets = [toHostConfigTarget(MCP_HOST_TARGETS["claude-code"])];
1950
2025
  const dirExists = async (rel) => {
1951
2026
  try {
1952
2027
  await deps.stat(path.join(deps.cwd, rel));
@@ -1957,10 +2032,10 @@ async function resolveHostConfigTargets(deps) {
1957
2032
  }
1958
2033
  };
1959
2034
  if (await dirExists(".vscode")) {
1960
- targets.push({ relPath: ".vscode/mcp.json", topLevelKey: "servers" });
2035
+ targets.push(toHostConfigTarget(MCP_HOST_TARGETS["copilot-vscode"]));
1961
2036
  }
1962
2037
  if ((await dirExists(".cursor")) || deps.env.CURSOR_TRACE_DIR) {
1963
- targets.push({ relPath: ".cursor/mcp.json", topLevelKey: "mcpServers" });
2038
+ targets.push(toHostConfigTarget(MCP_HOST_TARGETS.cursor));
1964
2039
  }
1965
2040
  return targets;
1966
2041
  }
@@ -2021,7 +2096,7 @@ export function buildInstallBridgeSecretFreeServerEntry(cwd, repoName, baseUrl,
2021
2096
  */
2022
2097
  function formatCredentialStoreGuidance(repoName, credentialStorePath) {
2023
2098
  return [
2024
- " The bridge-api server resolves your key at runtime from the BAPI_API_KEY environment",
2099
+ ` The ${MCP_SERVER_NAME} server resolves your key at runtime from the BAPI_API_KEY environment`,
2025
2100
  ` variable or the user-scoped credential store (${credentialStorePath}, target`,
2026
2101
  ` bapi:${repoName}), so it stays out of this file entirely.`,
2027
2102
  ];
@@ -2034,7 +2109,7 @@ function formatCredentialStoreGuidance(repoName, credentialStorePath) {
2034
2109
  * existing file — never for replacing it.
2035
2110
  */
2036
2111
  function formatSecretFreeManualMerge(relPath, topLevelKey, secretFreeEntry) {
2037
- const snippet = JSON.stringify({ [topLevelKey]: { "bridge-api": secretFreeEntry } }, null, 2);
2112
+ const snippet = JSON.stringify({ [topLevelKey]: { [MCP_SERVER_NAME]: secretFreeEntry } }, null, 2);
2038
2113
  return [
2039
2114
  ` To configure ${relPath} by hand, MERGE this secret-free entry into the existing`,
2040
2115
  " file (do not replace the file):",
@@ -2102,7 +2177,16 @@ async function detectExistingRealKey(deps, targets) {
2102
2177
  if (result.state === "absent")
2103
2178
  continue;
2104
2179
  const topLevel = asRecord(result.config[target.topLevelKey]);
2105
- const entry = asRecord(topLevel?.["bridge-api"]);
2180
+ // BAPI-807: a real key under a legacy `bridge-api` entry is just as much a
2181
+ // reason to ask for overwrite consent as one under `bridge`. A both-key
2182
+ // config is conservatively treated as possibly key-bearing for the same
2183
+ // reason `invalid` is.
2184
+ const resolution = resolveRegistrationKey(topLevel);
2185
+ if (resolution.state === "conflict")
2186
+ return true;
2187
+ if (resolution.state === "absent")
2188
+ continue;
2189
+ const entry = asRecord(topLevel?.[resolution.key]);
2106
2190
  const env = asRecord(entry?.["env"]);
2107
2191
  if (env && !isPlaceholderApiKey(env["BAPI_API_KEY"])) {
2108
2192
  return true;
@@ -2175,6 +2259,28 @@ export function preserveExistingLauncherArgs(existing, entry) {
2175
2259
  const preserved = refreshBridgeApiPackageSpec(existingArgs, currentBridgePackageSpec());
2176
2260
  return preserved ? { ...entry, args: preserved } : entry;
2177
2261
  }
2262
+ /**
2263
+ * Read-only pre-scan for both-key configs across EVERY project target (BAPI-807).
2264
+ *
2265
+ * Run before the registration phase begins writing, so a conflict discovered in
2266
+ * `.cursor/mcp.json` cannot leave `.mcp.json` and `.vscode/mcp.json` already
2267
+ * rewritten. A target that is absent or unparseable contributes no conflict —
2268
+ * those are handled by the existing per-target branches, which have their own
2269
+ * non-fatal semantics.
2270
+ */
2271
+ async function detectDuplicateRegistrations(deps, targets) {
2272
+ const conflicts = [];
2273
+ for (const target of targets) {
2274
+ const read = await readHostConfig(deps, path.join(deps.cwd, target.relPath));
2275
+ if (read.state !== "parsed")
2276
+ continue;
2277
+ const topLevel = asRecord(read.config[target.topLevelKey]);
2278
+ if (resolveRegistrationKey(topLevel).state === "conflict") {
2279
+ conflicts.push(target.relPath);
2280
+ }
2281
+ }
2282
+ return conflicts;
2283
+ }
2178
2284
  /**
2179
2285
  * Write the `bridge-api` entry into each host config via read-merge-write,
2180
2286
  * preserving unrelated servers and top-level keys (BAPI-666). Per target:
@@ -2192,6 +2298,13 @@ export function preserveExistingLauncherArgs(existing, entry) {
2192
2298
  async function writeHostConfigs(deps, targets, entries, trackedState, ctx) {
2193
2299
  const written = [];
2194
2300
  const skipped = [];
2301
+ // BAPI-807: detect both-key configs across ALL targets BEFORE the first write.
2302
+ // A multi-target registration phase that discovered the conflict on its third
2303
+ // target would already have rewritten the first two.
2304
+ const duplicateRegistrations = await detectDuplicateRegistrations(deps, targets);
2305
+ if (duplicateRegistrations.length > 0) {
2306
+ return { written, skipped, duplicateRegistrations };
2307
+ }
2195
2308
  for (const target of targets) {
2196
2309
  const fullPath = path.join(deps.cwd, target.relPath);
2197
2310
  const read = await readHostConfig(deps, fullPath);
@@ -2235,21 +2348,33 @@ async function writeHostConfigs(deps, targets, entries, trackedState, ctx) {
2235
2348
  // never initialized before its tracked-state consent is resolved.
2236
2349
  const config = read.state === "parsed" ? read.config : {};
2237
2350
  const topLevel = asRecord(config[target.topLevelKey]) ?? {};
2351
+ // BAPI-807: resolve WHICH key this config's registration lives under before
2352
+ // writing. A legacy-only entry is replaced under `bridge-api`, a canonical-only
2353
+ // entry under `bridge`, and an absent one is created under MCP_SERVER_NAME.
2354
+ const resolution = resolveRegistrationKey(topLevel);
2355
+ if (resolution.state === "conflict") {
2356
+ // Unreachable: the pre-scan above returned before any write when it found a
2357
+ // conflict. Re-checked rather than asserted, so a future caller that skips
2358
+ // the pre-scan still cannot write over an ambiguous registration.
2359
+ skipped.push({ relPath: target.relPath, reason: "invalid" });
2360
+ continue;
2361
+ }
2362
+ const registrationKey = resolution.state === "absent" ? resolution.writeKey : resolution.key;
2238
2363
  // BAPI-714 (Group C): a PRE-EXISTING Bridge entry keeps its own launcher args
2239
2364
  // composition — only the package-spec token is refreshed — so the installer
2240
2365
  // never migrates a legacy bare launcher to `serve`. A newly created entry gets
2241
2366
  // the full template args, `serve` included. The rule is identical for the
2242
2367
  // real-key and secret-free variants: credential safety must not decide whether
2243
2368
  // a launcher is migrated.
2244
- topLevel["bridge-api"] = preserveExistingLauncherArgs(topLevel["bridge-api"], entry);
2369
+ topLevel[registrationKey] = preserveExistingLauncherArgs(topLevel[registrationKey], entry);
2245
2370
  config[target.topLevelKey] = topLevel;
2246
2371
  await deps.mkdir(path.dirname(fullPath), { recursive: true });
2247
2372
  await deps.writeFile(fullPath, JSON.stringify(config, null, 2) + "\n", {
2248
2373
  encoding: "utf-8",
2249
2374
  });
2250
- written.push({ relPath: target.relPath, mode });
2375
+ written.push({ relPath: target.relPath, mode, registrationKey });
2251
2376
  }
2252
- return { written, skipped };
2377
+ return { written, skipped, duplicateRegistrations: [] };
2253
2378
  }
2254
2379
  /**
2255
2380
  * Provision the selected GLOBAL (Codex, Copilot CLI) and MANUAL (Windsurf)
@@ -2266,6 +2391,7 @@ async function writeHostConfigs(deps, targets, entries, trackedState, ctx) {
2266
2391
  */
2267
2392
  async function provisionSelectedGlobalTargets(deps, platforms, entry, needKey) {
2268
2393
  const logLines = [];
2394
+ const duplicateRegistrations = [];
2269
2395
  let anyManualRequired = false;
2270
2396
  const provisionDeps = {
2271
2397
  fs: {
@@ -2286,7 +2412,7 @@ async function provisionSelectedGlobalTargets(deps, platforms, entry, needKey) {
2286
2412
  continue;
2287
2413
  const target = MCP_HOST_TARGETS[id];
2288
2414
  // Skip project JSON targets — those are handled by writeHostConfigs.
2289
- if (target.scope === "project" && target.format === "json")
2415
+ if (target.scope === "project" && hostAdapterForTarget(target).format === "json")
2290
2416
  continue;
2291
2417
  const outcome = await provisionHostTarget(target, entry, provisionDeps);
2292
2418
  switch (outcome.status) {
@@ -2297,12 +2423,19 @@ async function provisionSelectedGlobalTargets(deps, platforms, entry, needKey) {
2297
2423
  break;
2298
2424
  case "manual-required":
2299
2425
  anyManualRequired = true;
2300
- logLines.push(` ${target.label}: add the bridge-api MCP server manually to ${outcome.displayPath} ` +
2426
+ logLines.push(` ${target.label}: add the ${MCP_SERVER_NAME} MCP server manually to ${outcome.displayPath} ` +
2301
2427
  "(the API key is redacted in printed instructions).");
2302
2428
  break;
2303
2429
  case "skipped-invalid":
2304
2430
  logLines.push(` ${target.label}: skipped ${outcome.displayPath} — existing config is not valid; left untouched.`);
2305
2431
  break;
2432
+ case "skipped-duplicate-registration":
2433
+ // BAPI-807: NOT an ordinary vendor-fallback condition. The target was
2434
+ // left byte-identical and the caller must fail loudly rather than report
2435
+ // a configured host.
2436
+ duplicateRegistrations.push(target.displayPath);
2437
+ logLines.push(` ${target.label}: skipped ${outcome.displayPath} — ${outcome.detail}`);
2438
+ break;
2306
2439
  case "failed":
2307
2440
  logLines.push(` ${target.label}: could not be configured automatically; configure it manually.`);
2308
2441
  break;
@@ -2311,7 +2444,7 @@ async function provisionSelectedGlobalTargets(deps, platforms, entry, needKey) {
2311
2444
  if (needKey && anyManualRequired) {
2312
2445
  logLines.push(` ${formatNeedKeyCredentialStoreLine(needKey.repoName, needKey.credentialStorePath)}`);
2313
2446
  }
2314
- return logLines;
2447
+ return { logLines, duplicateRegistrations };
2315
2448
  }
2316
2449
  /** Build the `/jira/ping` URL exactly like the MCP `ping` tool / buildGetUrl. */
2317
2450
  export function buildPingUrl(baseUrl, repoName) {
@@ -2842,7 +2975,7 @@ export const INSTALL_BRIDGE_SIGNUP_UPGRADE_REQUIRED = [
2842
2975
  "Signup now verifies your email address with a code before creating a workspace. Upgrade",
2843
2976
  "and re-run:",
2844
2977
  "",
2845
- " npx -y @bridge_gpt/mcp-server@latest install --email <addr>",
2978
+ ` npx -y ${MCP_PACKAGE_NAME}@latest install --email <addr>`,
2846
2979
  ].join("\n");
2847
2980
  /**
2848
2981
  * Mask an address for display: first character, then the domain.
@@ -3619,11 +3752,11 @@ function buildManualHostInstructions(entry, editors, needKey) {
3619
3752
  "Add the server manually (replace <REDACTED> with your key):",
3620
3753
  ];
3621
3754
  if (editors.windsurf) {
3622
- const windsurfSnippet = JSON.stringify({ mcpServers: { "bridge-api": { command: entry.command, args: entry.args, env: redactedEnv } } }, null, 2);
3755
+ const windsurfSnippet = JSON.stringify({ mcpServers: { [MCP_SERVER_NAME]: { command: entry.command, args: entry.args, env: redactedEnv } } }, null, 2);
3623
3756
  lines.push("", " Windsurf → ~/.codeium/windsurf/mcp_config.json:", windsurfSnippet);
3624
3757
  }
3625
3758
  if (editors.codex) {
3626
- lines.push("", " Codex → ~/.codex/config.toml (add an [mcp_servers.bridge-api] table with the", " same command/args/env shown above, BAPI_API_KEY set to your key).");
3759
+ lines.push("", ` Codex → ~/.codex/config.toml (add an [mcp_servers.${MCP_SERVER_NAME}] table with the`, " same command/args/env shown above, BAPI_API_KEY set to your key).");
3627
3760
  }
3628
3761
  if (needKey) {
3629
3762
  lines.push("", ` ${formatNeedKeyCredentialStoreLine(needKey.repoName, needKey.credentialStorePath)}`);
@@ -3638,7 +3771,7 @@ function buildManualHostInstructions(entry, editors, needKey) {
3638
3771
  * ONCE and reused by the Step 1–5 catch, the `index.ts` dispatch catch, the
3639
3772
  * tests, and the feature documentation, so the command text cannot drift.
3640
3773
  */
3641
- export const INSTALL_BRIDGE_DOCTOR_COMMAND = "npx -y @bridge_gpt/mcp-server doctor";
3774
+ export const INSTALL_BRIDGE_DOCTOR_COMMAND = `npx -y ${MCP_PACKAGE_NAME} doctor`;
3642
3775
  /**
3643
3776
  * The canonical pointer LINE (BAPI-669, U9a). One syntax — no quotes, no alternate
3644
3777
  * punctuation — shared by the Step 1–5 catch and by every operational fatal in the
@@ -3980,6 +4113,9 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
3980
4113
  // seams above are: a test that overrides only `fetch` or only `homedir`
3981
4114
  // must see its override here, not a stale default captured earlier.
3982
4115
  executorProvisioning: buildConductorExecutorDeps(deps, conductorBaseUrl),
4116
+ // Built from the FINAL merged deps for the same reason (BAPI-775): a test
4117
+ // that overrides only `readFile` or only `cwd` must see its override here.
4118
+ localProvisioning: buildConductorLocalDeps(deps),
3983
4119
  };
3984
4120
  return runInstallBridgeConductorCli(argv.slice(1), conductorDeps);
3985
4121
  }
@@ -5072,6 +5208,18 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
5072
5208
  trackedState.set(target.relPath, await isTrackedProjectConfig(deps, target.relPath));
5073
5209
  }
5074
5210
  const writeResult = await writeHostConfigs(deps, targets, { real: entry, secretFree: secretFreeEntry }, trackedState, { repoName, credentialStorePath });
5211
+ // BAPI-807: a both-key project config is a FAIL-LOUD condition, not an
5212
+ // ordinary skip. Nothing was written (the pre-write scan returned before the
5213
+ // first target), and continuing would report a successful install over a
5214
+ // registration Bridge deliberately refused to touch.
5215
+ if (writeResult.duplicateRegistrations.length > 0) {
5216
+ errorLog(`Error: ${DUPLICATE_REGISTRATION_GUIDANCE}`);
5217
+ for (const relPath of writeResult.duplicateRegistrations) {
5218
+ errorLog(` ${relPath}`);
5219
+ }
5220
+ errorLog(" Nothing was written. Re-run install-bridge once one entry is removed.");
5221
+ return 1;
5222
+ }
5075
5223
  // BAPI-708 (B-a): these files are the MCP SERVER REGISTRATION (BAPI_REPO_NAME,
5076
5224
  // BAPI_BASE_URL, BAPI_DOCS_DIR, BAPI_PROJECT_ROOT, and the version-pinned npx
5077
5225
  // launcher) — calling them a credentials file was simply inaccurate. Where the
@@ -5094,9 +5242,20 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
5094
5242
  const needKeyManual = bootstrapInviteMode
5095
5243
  ? { repoName, credentialStorePath }
5096
5244
  : undefined;
5097
- const globalLogLines = await provisionSelectedGlobalTargets(deps, selectedPlatforms, entry, needKeyManual);
5098
- for (const line of globalLogLines)
5245
+ const globalProvisioning = await provisionSelectedGlobalTargets(deps, selectedPlatforms, entry, needKeyManual);
5246
+ for (const line of globalProvisioning.logLines)
5099
5247
  log(line);
5248
+ // BAPI-807: a global target that refused because it already carries both
5249
+ // registrations is fail-loud, exactly like the project-config case above —
5250
+ // never an ordinary vendor fallback the run can shrug off.
5251
+ if (globalProvisioning.duplicateRegistrations.length > 0) {
5252
+ errorLog(`Error: ${DUPLICATE_REGISTRATION_GUIDANCE}`);
5253
+ for (const displayPath of globalProvisioning.duplicateRegistrations) {
5254
+ errorLog(` ${displayPath}`);
5255
+ }
5256
+ errorLog(" That host was left untouched. Re-run install-bridge once one entry is removed.");
5257
+ return 1;
5258
+ }
5100
5259
  // Legacy manual-editor instructions cover editors that are DETECTED but were
5101
5260
  // NOT part of the selection (so the emitter above did not handle them). The two
5102
5261
  // editors are suppressed INDEPENDENTLY: Codex is dropped when it was selected
@@ -5187,7 +5346,7 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
5187
5346
  `start-tickets model routing may not resolve the key for bapi:${repoName} ` +
5188
5347
  "and will fail open to the premium/Opus tier (the most expensive) — " +
5189
5348
  "set BAPI_API_KEY in the shell or re-run install, then verify with " +
5190
- "'npx -y @bridge_gpt/mcp-server doctor'.");
5349
+ `'npx -y ${MCP_PACKAGE_NAME} doctor'.`);
5191
5350
  }
5192
5351
  }
5193
5352
  catch {
@@ -5196,7 +5355,7 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
5196
5355
  log(" warning: could not persist the routing credential (unexpected error). " +
5197
5356
  "start-tickets model routing may need BAPI_API_KEY in the shell and will fail open " +
5198
5357
  "to the premium/Opus tier (the most expensive) until fixed — verify with " +
5199
- "'npx -y @bridge_gpt/mcp-server doctor'.");
5358
+ `'npx -y ${MCP_PACKAGE_NAME} doctor'.`);
5200
5359
  }
5201
5360
  }
5202
5361
  // ---- commit-your-assets notice (BAPI-708, Part C) ----
@@ -5280,7 +5439,7 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
5280
5439
  "server provides.");
5281
5440
  // The trust dialog is the single most common place a spawned session stalls:
5282
5441
  // the tab opens, nothing is approved, and the install looks hung.
5283
- log(`In the new tab: approve the workspace and the 'bridge-api' MCP server if ${handoffToolLabel} ` +
5442
+ log(`In the new tab: approve the workspace and the '${MCP_SERVER_NAME}' MCP server if ${handoffToolLabel} ` +
5284
5443
  "asks — configuration can't proceed until you do.");
5285
5444
  // Recovery was previously printed ONLY on no-spawn paths, so a tab that opened
5286
5445
  // and then died left the user with no instruction at all.
@@ -5304,6 +5463,25 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
5304
5463
  return 0;
5305
5464
  }
5306
5465
  catch (error) {
5466
+ // BAPI-807: a launcher-config PREFLIGHT refusal is actionable, and the
5467
+ // operator must be told what to do — collapsing it into the generic
5468
+ // "unexpected error" cause is how a duplicate registration would look like
5469
+ // a bug in Bridge instead of two entries in their own config.
5470
+ //
5471
+ // This is safe under the module's secret-suppression discipline because
5472
+ // `InitPreflightError` is a TYPE this codebase owns, and its payload is a
5473
+ // closed set: a repo-relative config path plus a reason string drawn from
5474
+ // the fixed `REASON_TEXT` table. No caught free-form message, no config
5475
+ // body, and no env value is read here.
5476
+ if (error instanceof InitPreflightError) {
5477
+ errorLog("Error: these MCP launcher configs cannot be safely reconciled:");
5478
+ for (const failure of error.failures) {
5479
+ errorLog(` ${failure.relPath}: ${failure.reason}`);
5480
+ }
5481
+ errorLog(" Nothing was changed. Resolve these by hand, then re-run install-bridge.");
5482
+ errorLog(INSTALL_BRIDGE_DOCTOR_POINTER);
5483
+ return 1;
5484
+ }
5307
5485
  // Resume guidance ONLY after the exchange actually succeeded (R5-2): before
5308
5486
  // that the invite is unspent and there is nothing to resume. The wording comes
5309
5487
  // from the mode-aware helper, never a fresh literal, so a self-serve user is
@@ -22,6 +22,8 @@
22
22
  import path from "path";
23
23
  import { resolveBapiCredentials } from "./credential-store.js";
24
24
  import { detectClaudeLogin, formatClaudeLoginAdvisory } from "./claude-login.js";
25
+ import { getProjectJsonTargets, hostAdapterForTarget } from "./mcp-host-targets.js";
26
+ import { existingRegistrationKey, MCP_PACKAGE_NAME, } from "./mcp-identity.js";
25
27
  /** Default production base URL (mirrors install-bridge's DEFAULT_BAPI_BASE_URL). */
26
28
  const DEFAULT_BASE_URL = "https://bridgegpt-api.com";
27
29
  /**
@@ -36,12 +38,15 @@ const DEFAULT_BASE_URL = "https://bridgegpt-api.com";
36
38
  function buildSetupUrl(baseUrl) {
37
39
  return `${baseUrl.replace(/\/+$/, "")}/setup`;
38
40
  }
39
- /** Project-local MCP configs whose env blocks may carry repo/base-URL values. */
40
- const MCP_CONFIG_ENV_TARGETS = [
41
- { relPath: ".mcp.json", topLevelKey: "mcpServers" },
42
- { relPath: ".cursor/mcp.json", topLevelKey: "mcpServers" },
43
- { relPath: ".vscode/mcp.json", topLevelKey: "servers" },
44
- ];
41
+ /**
42
+ * Project-local MCP configs whose env blocks may carry repo/base-URL values.
43
+ * Derived from the shared host-target registry's project-scoped JSON targets
44
+ * rather than duplicating each path/root-key pair here.
45
+ */
46
+ const MCP_CONFIG_ENV_TARGETS = getProjectJsonTargets().map((target) => ({
47
+ relPath: target.relPath,
48
+ topLevelKey: hostAdapterForTarget(target).topLevelKey,
49
+ }));
45
50
  /** Timeout for each read-only probe request. */
46
51
  const PROBE_TIMEOUT_MS = 5_000;
47
52
  /**
@@ -72,9 +77,18 @@ export async function resolveInstallDoctorTarget(deps) {
72
77
  catch {
73
78
  continue;
74
79
  }
75
- const envBlock = parsed && typeof parsed === "object"
80
+ // BAPI-807: read the env block through the RESOLVED registration key. A
81
+ // config written before the rename registers `bridge-api`, and looking
82
+ // only under the canonical name would report a perfectly working install
83
+ // as having no repo/base-URL configured at all.
84
+ const servers = parsed && typeof parsed === "object"
85
+ ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
+ parsed[topLevelKey]
87
+ : undefined;
88
+ const registrationKey = existingRegistrationKey(servers);
89
+ const envBlock = registrationKey !== null
76
90
  ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
- parsed[topLevelKey]?.["bridge-api"]?.env
91
+ servers[registrationKey]?.env
78
92
  : undefined;
79
93
  if (!envBlock)
80
94
  continue;
@@ -410,7 +424,7 @@ export async function collectInstallStatusChecks(deps) {
410
424
  label: "GitHub connection",
411
425
  status: "WARN",
412
426
  detail: "no GitHub repository is connected to this project",
413
- remediation: `run 'npx -y @bridge_gpt/mcp-server@latest connect-github --repo ${target.repoName}'.`,
427
+ remediation: `run 'npx -y ${MCP_PACKAGE_NAME}@latest connect-github --repo ${target.repoName}'.`,
414
428
  });
415
429
  }
416
430
  }
@@ -40,6 +40,7 @@ import { VERSION } from "./version.generated.js";
40
40
  import { isNewerVersion } from "./update-check.js";
41
41
  import { fetchLatestVersion } from "./cli-release.js";
42
42
  import { runInstallBridgeCli } from "./install-bridge.js";
43
+ import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
43
44
  /** The sentinel that marks an already-re-exec'd child. Matches `upgrade-cli.ts`. */
44
45
  export const INSTALL_REEXEC_SENTINEL = "--internal-reexec";
45
46
  /**
@@ -189,7 +190,7 @@ export async function runInstallBridgeWithLatestCli(argv, deps = {}) {
189
190
  const npxCmd = platform === "win32" ? "npx.cmd" : "npx";
190
191
  const childArgs = [
191
192
  "-y",
192
- "@bridge_gpt/mcp-server@latest",
193
+ `${MCP_PACKAGE_NAME}@latest`,
193
194
  "install",
194
195
  INSTALL_REEXEC_SENTINEL,
195
196
  ...forwardedArgs,