@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.
- package/README.md +330 -191
- package/build/agent-capabilities/cli.js +2 -1
- package/build/agent-launchers/claude-executor-adapter.js +17 -4
- package/build/agents.generated.js +2 -2
- package/build/claude-review-workflow.js +510 -45
- package/build/claude-user-config-doctor.js +42 -11
- package/build/cli-release.js +2 -1
- package/build/commands.generated.js +6 -5
- package/build/conduct-epic/bridge-client.js +354 -113
- package/build/conduct-epic/checkpoint-store.js +17 -0
- package/build/conduct-epic/cli.js +947 -99
- package/build/conduct-epic/cut-protocol.js +327 -0
- package/build/conduct-epic/spawn.js +14 -2
- package/build/conductor/bridge-api-client.js +148 -1
- package/build/conductor/cli.js +109 -1
- package/build/conductor/doctor.js +101 -16
- package/build/conductor/epic-reconcile.js +72 -19
- package/build/conductor/epic-runtime.js +15 -3
- package/build/conductor/errors.js +47 -0
- package/build/conductor/git-hooks.js +205 -11
- package/build/conductor/install-doctor.js +230 -1
- package/build/conductor/local-merge.js +130 -28
- package/build/conductor/recovery-cli.js +313 -0
- package/build/conductor/recovery-operations.js +219 -0
- package/build/conductor/tools.js +32 -3
- package/build/conductor/worker-ledger-cli.js +27 -1
- package/build/conductor-bin.js +20 -16
- package/build/credentials-cli.js +3 -2
- package/build/docs.generated.js +2 -1
- package/build/doctor.js +120 -44
- package/build/drive-epic.js +375 -0
- package/build/executor/cli.js +48 -1
- package/build/executor/env.js +21 -0
- package/build/executor/http-client.js +71 -3
- package/build/executor/index-scope.js +39 -0
- package/build/executor/job-errors.js +9 -0
- package/build/executor/job-log-registry.js +69 -0
- package/build/executor/job-runner.js +198 -29
- package/build/executor/live-worker-registry.js +83 -0
- package/build/executor/observation.js +259 -6
- package/build/executor/platform.js +147 -3
- package/build/executor/process.js +58 -14
- package/build/executor/runner.js +454 -48
- package/build/executor/test-clock.js +3 -2
- package/build/executor/worker-finalization.js +233 -56
- package/build/executor/worktree.js +8 -1
- package/build/index-scope-contract.js +96 -0
- package/build/index.js +2277 -270
- package/build/init.js +83 -22
- package/build/install-bridge-conductor.js +323 -14
- package/build/install-bridge.js +225 -47
- package/build/install-doctor.js +23 -9
- package/build/install-reexec.js +2 -1
- package/build/launcher-config-inspection.js +83 -22
- package/build/mcp-host-config.js +331 -67
- package/build/mcp-host-targets.js +45 -21
- package/build/mcp-identity.js +92 -0
- package/build/mcp-install-state.js +94 -1
- package/build/mcp-invoke.js +2 -1
- package/build/mcp-provisioning.js +45 -12
- package/build/mcp-registration-doctor.js +35 -13
- package/build/mcp-server-invocation.js +4 -2
- package/build/merge-pull-request.js +208 -9
- package/build/pipelines.generated.js +305 -15
- package/build/plane/cli.js +73 -7
- package/build/plane/defaults.js +18 -5
- package/build/plane/manifest.js +90 -0
- package/build/plane/preflight.js +100 -10
- package/build/plane/shutdown.js +71 -3
- package/build/plane/test-fakes.js +9 -1
- package/build/readme.generated.js +1 -1
- package/build/regression-check.js +3 -2
- package/build/review-tickets.js +8 -7
- package/build/run-unit-tests-launcher.js +149 -6
- package/build/schedule-run.js +3 -2
- package/build/setup-epic.js +531 -82
- package/build/sfcc/tool-wrapper.js +15 -0
- package/build/start-tickets-prereqs.js +11 -6
- package/build/start-tickets.js +91 -85
- package/build/update-check.js +3 -2
- package/build/upgrade-advice.js +2 -1
- package/build/upgrade-cli.js +50 -18
- package/build/version.generated.js +2 -1
- package/build/worktree-core.js +31 -17
- package/docs/CONDUCTOR.md +22 -0
- package/docs/install/mcp-tool-integrations.md +19 -3
- package/package.json +2 -2
- package/pipelines/greenfield-setup.json +286 -0
package/build/install-bridge.js
CHANGED
|
@@ -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 {
|
|
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",
|
|
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,
|
|
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
|
|
220
|
-
"
|
|
221
|
-
"
|
|
222
|
-
"
|
|
223
|
-
"
|
|
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
|
-
|
|
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
|
|
259
|
-
"
|
|
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
|
-
|
|
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),
|
|
506
|
-
"
|
|
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 =
|
|
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 @
|
|
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 @
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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]: {
|
|
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
|
-
|
|
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[
|
|
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
|
|
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
|
-
|
|
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: {
|
|
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("",
|
|
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 =
|
|
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
|
|
5098
|
-
for (const line of
|
|
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
|
-
|
|
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
|
-
|
|
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 '
|
|
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
|
package/build/install-doctor.js
CHANGED
|
@@ -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
|
-
/**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 @
|
|
427
|
+
remediation: `run 'npx -y ${MCP_PACKAGE_NAME}@latest connect-github --repo ${target.repoName}'.`,
|
|
414
428
|
});
|
|
415
429
|
}
|
|
416
430
|
}
|
package/build/install-reexec.js
CHANGED
|
@@ -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
|
-
|
|
193
|
+
`${MCP_PACKAGE_NAME}@latest`,
|
|
193
194
|
"install",
|
|
194
195
|
INSTALL_REEXEC_SENTINEL,
|
|
195
196
|
...forwardedArgs,
|