@bridge_gpt/mcp-server 0.2.18 → 0.2.20
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/CONDUCTOR.md +75 -0
- package/README.md +8 -5
- package/build/agent-capabilities/probe-context.js +13 -3
- package/build/agent-capabilities/probes.js +262 -11
- package/build/agent-capabilities/reporter.js +1 -0
- package/build/agents.generated.js +1 -1
- package/build/backend-warnings.js +44 -0
- package/build/claude-settings.js +129 -0
- package/build/commands.generated.js +5 -3
- package/build/conductor/bridge-api-client.js +7 -7
- package/build/conductor/cli.js +65 -12
- package/build/conductor/deny-enforcement-preflight.js +96 -0
- package/build/conductor/doctor.js +183 -2
- package/build/conductor/epic-reconcile.js +9 -1
- package/build/conductor/epic-runtime.js +403 -43
- package/build/conductor/epic-state.js +7 -0
- package/build/conductor/errors.js +115 -3
- package/build/conductor/event-accessors.js +28 -10
- package/build/conductor/local-merge.js +458 -95
- package/build/conductor/merge-ledger.js +6 -4
- package/build/conductor/pr-ci-producer.js +17 -2
- package/build/conductor/producer-ledger.js +1 -1
- package/build/conductor/store.js +161 -18
- package/build/conductor/supervisor-merge.js +32 -5
- package/build/conductor/taxonomy.js +8 -0
- package/build/conductor/tools.js +28 -6
- package/build/conductor/worker-ledger-cli.js +244 -0
- package/build/conductor-bin.js +1884 -6917
- package/build/doctor.js +8 -0
- package/build/estimate-epic.js +84 -0
- package/build/executor/cli.js +229 -0
- package/build/executor/credentials.js +65 -0
- package/build/executor/deps.js +117 -0
- package/build/executor/env.js +79 -0
- package/build/executor/heartbeat.js +59 -0
- package/build/executor/http-client.js +131 -0
- package/build/executor/index.js +10 -0
- package/build/executor/job-errors.js +55 -0
- package/build/executor/job-log-registry.js +110 -0
- package/build/executor/job-runner.js +822 -0
- package/build/executor/job-types.js +60 -0
- package/build/executor/merge-job.js +229 -0
- package/build/executor/observation.js +123 -0
- package/build/executor/permissions.js +79 -0
- package/build/executor/preflight.js +144 -0
- package/build/executor/process.js +81 -0
- package/build/executor/prompt-spec.js +235 -0
- package/build/executor/results.js +134 -0
- package/build/executor/resume-pre-spawn.js +179 -0
- package/build/executor/runner.js +98 -0
- package/build/executor/terminal-mutation.js +34 -0
- package/build/executor/test-clock.js +109 -0
- package/build/executor/types.js +18 -0
- package/build/executor/verdict-artifact.js +53 -0
- package/build/executor/viewer-tabs.js +78 -0
- package/build/executor/watch-cli.js +113 -0
- package/build/executor/worker-command.js +106 -0
- package/build/executor/worker-finalization.js +177 -0
- package/build/executor/worker-log.js +92 -0
- package/build/executor/worktree-gc.js +134 -0
- package/build/executor/worktree-inspection.js +86 -0
- package/build/executor/worktree.js +103 -0
- package/build/index.js +14534 -10416
- package/build/mcp-invoke.js +19 -3
- package/build/mcp-provisioning.js +31 -25
- package/build/mcp-registration-doctor.js +27 -7
- package/build/mcp-server-invocation.js +152 -0
- package/build/pipelines.generated.js +16 -20
- package/build/readme.generated.js +1 -1
- package/build/sfcc/client.js +192 -50
- package/build/sfcc/ocapi-write-faults.js +94 -0
- package/build/sfcc/permissions.js +7 -22
- package/build/sfcc/reads-site-preference.js +52 -19
- package/build/sfcc/register.js +9 -0
- package/build/sfcc/write-grants.js +80 -0
- package/build/sfcc/write-guard.js +39 -0
- package/build/sfcc/write-result.js +47 -0
- package/build/sfcc/write-tool-common.js +85 -0
- package/build/sfcc/writes-custom-object-def.js +141 -0
- package/build/sfcc/writes-object-attribute-payloads.js +97 -0
- package/build/sfcc/writes-site-preference-payloads.js +59 -0
- package/build/sfcc/writes-site-preference.js +96 -0
- package/build/sfcc/writes-system-object-payloads.js +213 -0
- package/build/sfcc/writes-system-object.js +348 -0
- package/build/sfcc/writes.js +66 -0
- package/build/start-tickets-conductor.js +25 -93
- package/build/start-tickets-prereqs.js +152 -1
- package/build/start-tickets.js +96 -158
- package/build/version.generated.js +1 -1
- package/build/visual-diff-worker.js +313 -0
- package/build/visual-diff.js +632 -0
- package/build/worktree-core.js +202 -0
- package/package.json +8 -4
- package/pipelines/idea-to-ticket.json +7 -0
- package/pipelines/review-ticket.json +5 -18
- package/public/css/main.min.css +1612 -107
- package/public/css/main.min.css.map +1 -1
- package/public/js/main.min.js +10267 -1
- package/public/js/main.min.js.map +1 -1
- package/smoke-test/SMOKE-TEST.md +2 -1
package/build/mcp-invoke.js
CHANGED
|
@@ -32,10 +32,15 @@ import { getThirdPartyTargetDefinition, resolveThirdPartyTargetEnv, validateThir
|
|
|
32
32
|
export function getMcpInvokeUsage() {
|
|
33
33
|
return [
|
|
34
34
|
"Usage:",
|
|
35
|
-
"
|
|
35
|
+
" node <abs>/mcp_server/build/index.js mcp-invoke --target <target> --project-root <ABS_WORKTREE_PATH>",
|
|
36
|
+
"",
|
|
37
|
+
" (npm-channel fallback may invoke the same shim through a package spec, e.g.",
|
|
38
|
+
" npx -y @bridge_gpt/mcp-server@latest mcp-invoke --target <target> --project-root <ABS_WORKTREE_PATH>)",
|
|
36
39
|
"",
|
|
37
40
|
"Internal worktree shim: resolves the target's launch command and credentials",
|
|
38
41
|
"from the given project root, then spawns the real MCP server over stdio.",
|
|
42
|
+
"Argument parsing is identical regardless of how this process was launched",
|
|
43
|
+
"(absolute-path node invocation vs npm-channel npx).",
|
|
39
44
|
"",
|
|
40
45
|
"Flags:",
|
|
41
46
|
" --target <target> MCP target to launch. 'bapi' launches the Bridge",
|
|
@@ -264,11 +269,22 @@ export async function resolveBapiInvocation(projectRoot, deps) {
|
|
|
264
269
|
if (!credResult.ok) {
|
|
265
270
|
return { ok: false, error: `failed to resolve credentials: ${credResult.error}` };
|
|
266
271
|
}
|
|
267
|
-
const
|
|
272
|
+
const overlay = {
|
|
268
273
|
BAPI_API_KEY: credResult.credentials.apiKey,
|
|
269
274
|
BAPI_PROJECT_ROOT: projectRoot,
|
|
270
275
|
BAPI_REPO_NAME: repoName,
|
|
271
|
-
}
|
|
276
|
+
};
|
|
277
|
+
// BAPI-527: `CONDUCTOR_NODE_PATH` is NON-SECRET operational metadata (the
|
|
278
|
+
// captured conductor Node path used by the worker-ledger CLI subprocess).
|
|
279
|
+
// `buildChildEnv` already inherits the full parent env, so this re-affirms the
|
|
280
|
+
// pass-through explicitly and ONLY when present — it is a non-secret allowlist
|
|
281
|
+
// entry, never a credential. It is emphatically NOT the MCP server's re-exec
|
|
282
|
+
// command: the spawned command stays `deps.execPath` + `[scriptPath]` below.
|
|
283
|
+
const conductorNodePath = deps.env.CONDUCTOR_NODE_PATH;
|
|
284
|
+
if (typeof conductorNodePath === "string" && conductorNodePath.trim().length > 0) {
|
|
285
|
+
overlay.CONDUCTOR_NODE_PATH = conductorNodePath;
|
|
286
|
+
}
|
|
287
|
+
const env = buildChildEnv(deps.env, overlay);
|
|
272
288
|
return {
|
|
273
289
|
ok: true,
|
|
274
290
|
invocation: { command: deps.execPath, args: [deps.scriptPath], env },
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
* to avoid a runtime import cycle.
|
|
19
19
|
*/
|
|
20
20
|
import path from "path";
|
|
21
|
-
import { VERSION } from "./version.generated.js";
|
|
22
21
|
import { readBridgeConfig } from "./bridge-config.js";
|
|
23
22
|
import { getThirdPartyTargetDefinition, validateThirdPartyTargetManifestEntry, } from "./third-party-mcp-targets.js";
|
|
23
|
+
import { buildMcpShimCommand, } from "./mcp-server-invocation.js";
|
|
24
24
|
// ---------------------------------------------------------------------------
|
|
25
25
|
// Pure helpers
|
|
26
26
|
// ---------------------------------------------------------------------------
|
|
@@ -55,27 +55,27 @@ export function serverNameForMcpTarget(target) {
|
|
|
55
55
|
return target === "bapi" ? "bridge-api" : target;
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
|
-
* Build a secret-free shim entry for any target
|
|
59
|
-
* carry the target name plus the absolute project root; there is
|
|
60
|
-
* no `env` block.
|
|
58
|
+
* Build a secret-free shim entry for any target from a structured invocation.
|
|
59
|
+
* The command/args carry the target name plus the absolute project root; there is
|
|
60
|
+
* intentionally no `env` block. The absolute-build-path form serializes as
|
|
61
|
+
* `{ command: "node", args: ["<abs>/build/index.js", "mcp-invoke", …] }` (the
|
|
62
|
+
* node executable is a single substitutable field), and the npm-channel fallback
|
|
63
|
+
* as `{ command: "npx", args: ["-y", "<spec>", "mcp-invoke", …] }`.
|
|
64
|
+
*
|
|
65
|
+
* BAPI-527: `.mcp.json` remains intentionally ENV-FREE. `CONDUCTOR_NODE_PATH`
|
|
66
|
+
* (the captured conductor Node path for the worker-ledger CLI subprocess) is NOT
|
|
67
|
+
* written here — it is inherited at runtime from the worker SHELL env that
|
|
68
|
+
* `start-tickets` injects at the spawn boundary, never stored as a registration
|
|
69
|
+
* value. Keeping the shim env-free preserves the credential-safety invariant
|
|
70
|
+
* (secrets are resolved by the `mcp-invoke` shim at launch, not persisted in the
|
|
71
|
+
* worktree) and equally avoids persisting non-secret operational metadata here.
|
|
61
72
|
*/
|
|
62
|
-
export function buildShimMcpServerEntry(target, absoluteWorktreePath) {
|
|
63
|
-
return
|
|
64
|
-
command: "npx",
|
|
65
|
-
args: [
|
|
66
|
-
"-y",
|
|
67
|
-
`@bridge_gpt/mcp-server@${VERSION}`,
|
|
68
|
-
"mcp-invoke",
|
|
69
|
-
"--target",
|
|
70
|
-
target,
|
|
71
|
-
"--project-root",
|
|
72
|
-
absoluteWorktreePath,
|
|
73
|
-
],
|
|
74
|
-
};
|
|
73
|
+
export function buildShimMcpServerEntry(target, absoluteWorktreePath, invocation) {
|
|
74
|
+
return buildMcpShimCommand(invocation, target, absoluteWorktreePath);
|
|
75
75
|
}
|
|
76
76
|
/** Back-compat wrapper around `buildShimMcpServerEntry("bapi", ...)`. */
|
|
77
|
-
export function buildBridgeApiShimMcpServerEntry(absoluteWorktreePath) {
|
|
78
|
-
return buildShimMcpServerEntry("bapi", absoluteWorktreePath);
|
|
77
|
+
export function buildBridgeApiShimMcpServerEntry(absoluteWorktreePath, invocation) {
|
|
78
|
+
return buildShimMcpServerEntry("bapi", absoluteWorktreePath, invocation);
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
81
|
* Convert all supported manifest `mcp` entries into a map of server name -> shim
|
|
@@ -84,12 +84,12 @@ export function buildBridgeApiShimMcpServerEntry(absoluteWorktreePath) {
|
|
|
84
84
|
* or incomplete non-`bapi` targets are skipped with a secret-free warning (they
|
|
85
85
|
* never appear in the returned entries).
|
|
86
86
|
*/
|
|
87
|
-
export function buildMcpServerEntriesForManifest(manifest, absoluteWorktreePath) {
|
|
87
|
+
export function buildMcpServerEntriesForManifest(manifest, absoluteWorktreePath, invocation) {
|
|
88
88
|
const entries = {};
|
|
89
89
|
const warnings = [];
|
|
90
90
|
for (const mcp of manifest.mcp) {
|
|
91
91
|
if (mcp.target === "bapi") {
|
|
92
|
-
entries[serverNameForMcpTarget("bapi")] = buildShimMcpServerEntry("bapi", absoluteWorktreePath);
|
|
92
|
+
entries[serverNameForMcpTarget("bapi")] = buildShimMcpServerEntry("bapi", absoluteWorktreePath, invocation);
|
|
93
93
|
continue;
|
|
94
94
|
}
|
|
95
95
|
const definition = getThirdPartyTargetDefinition(mcp.target);
|
|
@@ -102,9 +102,9 @@ export function buildMcpServerEntriesForManifest(manifest, absoluteWorktreePath)
|
|
|
102
102
|
warnings.push(`MCP target '${mcp.target}' registration skipped: ${validation.error}.`);
|
|
103
103
|
continue;
|
|
104
104
|
}
|
|
105
|
-
entries[serverNameForMcpTarget(mcp.target)] = buildShimMcpServerEntry(mcp.target, absoluteWorktreePath);
|
|
105
|
+
entries[serverNameForMcpTarget(mcp.target)] = buildShimMcpServerEntry(mcp.target, absoluteWorktreePath, invocation);
|
|
106
106
|
}
|
|
107
|
-
return { entries, warnings };
|
|
107
|
+
return { entries, warnings, registrationForm: invocation.form };
|
|
108
108
|
}
|
|
109
109
|
/** Both registration files written for every provisioned worktree. */
|
|
110
110
|
export function getWorktreeMcpRegistrationTargets(worktreePath, platform) {
|
|
@@ -305,7 +305,7 @@ export async function provisionMcpRegistrationForWorktree(row, deps) {
|
|
|
305
305
|
if (!normalized.ok) {
|
|
306
306
|
return { ...row, status: "spawn-failed", error: `MCP provisioning failed: ${normalized.error}` };
|
|
307
307
|
}
|
|
308
|
-
const built = buildMcpServerEntriesForManifest(read.manifest, normalized.path);
|
|
308
|
+
const built = buildMcpServerEntriesForManifest(read.manifest, normalized.path, deps.mcpServerInvocation);
|
|
309
309
|
if (Object.keys(built.entries).length === 0) {
|
|
310
310
|
// Nothing supported to write (e.g. a manifest with no bapi target and no
|
|
311
311
|
// supported Tier-2 targets). Surface any warnings but leave status unchanged.
|
|
@@ -318,10 +318,16 @@ export async function provisionMcpRegistrationForWorktree(row, deps) {
|
|
|
318
318
|
return { ...row, status: "spawn-failed", error: `MCP provisioning failed: ${result.error}` };
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
|
+
// Record the resolved registration form for audit (path-free — the form label
|
|
322
|
+
// only, never the absolute server entry path, which could leak a username).
|
|
323
|
+
let result = { ...row, mcpRegistrationForm: built.registrationForm };
|
|
324
|
+
result = withWarnings(result, built.warnings);
|
|
325
|
+
if (built.registrationForm !== "absolute-build-path") {
|
|
326
|
+
result = withWarning(result, "MCP registration used npm-channel fallback because an on-disk build entry was not resolvable.");
|
|
327
|
+
}
|
|
321
328
|
// Pre-approve the just-registered servers so Claude Code does not prompt to
|
|
322
329
|
// trust them in this fresh worktree path. Best-effort: a failure here only
|
|
323
330
|
// loses the convenience of skipping the prompt, so warn and still spawn.
|
|
324
|
-
let result = withWarnings(row, built.warnings);
|
|
325
331
|
const serverNames = Object.keys(built.entries);
|
|
326
332
|
const trust = await writeClaudeServerTrustSettings(normalized.path, serverNames, deps);
|
|
327
333
|
if (!trust.ok) {
|
|
@@ -35,22 +35,42 @@ function flagValue(args, flag) {
|
|
|
35
35
|
return undefined;
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
|
-
* True when `entry` is a secret-free Bridge API shim registration
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
38
|
+
* True when `entry` is a secret-free Bridge API shim registration pointing at
|
|
39
|
+
* THIS worktree. Two forms are accepted (BAPI-526):
|
|
40
|
+
*
|
|
41
|
+
* - Absolute-build-path (PRIMARY): an absolute `node` (or absolute Node-path)
|
|
42
|
+
* invocation of the conductor's own compiled server, e.g.
|
|
43
|
+
* `{ command: "node", args: ["<abs>/mcp_server/build/index.js", "mcp-invoke",
|
|
44
|
+
* "--target", "bapi", "--project-root", "<worktreeRoot>"] }`. `command` may be
|
|
45
|
+
* any non-empty string ("node" or a future absolute Node path); `args[0]` is a
|
|
46
|
+
* non-empty server entry path.
|
|
47
|
+
* - npm-channel (LEGACY/FALLBACK): `{ command: "npx", args: ["-y",
|
|
48
|
+
* "@bridge_gpt/mcp-server@<spec>", "mcp-invoke", "--target", "bapi",
|
|
49
|
+
* "--project-root", "<worktreeRoot>"] }`.
|
|
50
|
+
*
|
|
51
|
+
* In both forms `--target` must be `bapi` and `--project-root` must match the
|
|
52
|
+
* given worktree exactly. The server entry path is NOT required to sit under any
|
|
53
|
+
* particular username/path prefix.
|
|
42
54
|
*/
|
|
43
55
|
export function isBridgeApiShimEntry(entry, worktreeRoot) {
|
|
44
56
|
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
45
57
|
return false;
|
|
46
58
|
const candidate = entry;
|
|
47
|
-
if (candidate.command !== "
|
|
59
|
+
if (typeof candidate.command !== "string" || candidate.command.length === 0)
|
|
48
60
|
return false;
|
|
49
61
|
if (!Array.isArray(candidate.args))
|
|
50
62
|
return false;
|
|
51
63
|
const args = candidate.args.filter((a) => typeof a === "string");
|
|
52
|
-
if (
|
|
53
|
-
|
|
64
|
+
if (candidate.command === "npx") {
|
|
65
|
+
// npm-channel fallback: must reference the package spec.
|
|
66
|
+
if (!args.some((a) => a.startsWith("@bridge_gpt/mcp-server")))
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// Absolute-build-path: first arg must be a non-empty server entry path.
|
|
71
|
+
if (typeof args[0] !== "string" || args[0].length === 0)
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
54
74
|
if (!args.includes("mcp-invoke"))
|
|
55
75
|
return false;
|
|
56
76
|
if (flagValue(args, "--target") !== "bapi")
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared, dependency-injected model for how a dispatched worker launches the
|
|
3
|
+
* Bridge MCP `mcp-invoke` shim (BAPI-526, Change A).
|
|
4
|
+
*
|
|
5
|
+
* The conductor used to register each dispatched worktree's `.mcp.json` against
|
|
6
|
+
* an EXACT npm version (`npx -y @bridge_gpt/mcp-server@<VERSION> mcp-invoke …`).
|
|
7
|
+
* When that version was built but not yet published, `npx` returned ETARGET/404
|
|
8
|
+
* and the worker had NO Bridge MCP tools. This module lets provisioning instead
|
|
9
|
+
* invoke the conductor's own on-disk build by ABSOLUTE PATH
|
|
10
|
+
* (`node <abs>/mcp_server/build/index.js mcp-invoke …`) — removing the
|
|
11
|
+
* npm-registry publish dependency and guaranteeing the worker runs the exact MCP
|
|
12
|
+
* code the conductor is running. A resolvable npm-channel spec remains the
|
|
13
|
+
* fallback when an on-disk build entry cannot be resolved (published-package
|
|
14
|
+
* install).
|
|
15
|
+
*
|
|
16
|
+
* SCOPE BOUNDARY (BAPI-526 is detection/registration only): this selects WHICH
|
|
17
|
+
* server file is invoked (`node <absPath>/index.js` vs `npx @…`). It does NOT
|
|
18
|
+
* change WHICH Node binary runs that file — the `nodeExecutable` field is kept as
|
|
19
|
+
* a single substitutable value (never string-concatenated into the command) so
|
|
20
|
+
* the later Ticket 3 (P2) work can point it at `CONDUCTOR_NODE_PATH` without
|
|
21
|
+
* re-plumbing argv construction.
|
|
22
|
+
*/
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
export const MCP_SERVER_PACKAGE_NAME = "@bridge_gpt/mcp-server";
|
|
25
|
+
/**
|
|
26
|
+
* Resolvable npm-channel fallback spec. Deliberately a moving channel (`@latest`)
|
|
27
|
+
* rather than an exact generated version pin, so the fallback path can never
|
|
28
|
+
* reintroduce the ETARGET/404 failure the absolute-path form exists to remove.
|
|
29
|
+
*/
|
|
30
|
+
export const DEFAULT_MCP_SERVER_NPM_CHANNEL_SPEC = "@bridge_gpt/mcp-server@latest";
|
|
31
|
+
/**
|
|
32
|
+
* Build the concrete `{ command, args }` launch pair for a target + worktree from
|
|
33
|
+
* a structured invocation. Pure — no filesystem or process access. The
|
|
34
|
+
* `nodeExecutable` is returned as the standalone `command`, never concatenated
|
|
35
|
+
* with the server entry path, so `CONDUCTOR_NODE_PATH` can substitute it later.
|
|
36
|
+
*/
|
|
37
|
+
export function buildMcpShimCommand(invocation, target, absoluteWorktreePath) {
|
|
38
|
+
if (invocation.form === "absolute-build-path") {
|
|
39
|
+
return {
|
|
40
|
+
command: invocation.nodeExecutable,
|
|
41
|
+
args: [
|
|
42
|
+
invocation.serverEntryPath,
|
|
43
|
+
"mcp-invoke",
|
|
44
|
+
"--target",
|
|
45
|
+
target,
|
|
46
|
+
"--project-root",
|
|
47
|
+
absoluteWorktreePath,
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
command: "npx",
|
|
53
|
+
args: [
|
|
54
|
+
"-y",
|
|
55
|
+
invocation.packageSpec,
|
|
56
|
+
"mcp-invoke",
|
|
57
|
+
"--target",
|
|
58
|
+
target,
|
|
59
|
+
"--project-root",
|
|
60
|
+
absoluteWorktreePath,
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Secret-free, path-free audit label for summaries and diagnostics. Returns ONLY
|
|
66
|
+
* the registration form; never the server entry path, username, argv, or package
|
|
67
|
+
* cache path (any of which can leak a username through a filesystem path).
|
|
68
|
+
*/
|
|
69
|
+
export function formatMcpShimInvocationForAudit(invocation) {
|
|
70
|
+
return invocation.form;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Resolve the package root from a module URL by walking the module file path and
|
|
74
|
+
* locating the nearest `src` or `build` path segment; the package root is the
|
|
75
|
+
* portion BEFORE that segment. Returns forward-slash form so it is deterministic
|
|
76
|
+
* across platforms (Node accepts forward slashes on Windows). Does NOT consult
|
|
77
|
+
* `process.cwd()`. Returns `null` when neither `src` nor `build` appears.
|
|
78
|
+
*/
|
|
79
|
+
export function resolvePackageRootFromModuleUrl(moduleUrl) {
|
|
80
|
+
// Parse the URL's pathname directly rather than via fileURLToPath: on Windows,
|
|
81
|
+
// fileURLToPath throws ERR_INVALID_FILE_URL_PATH for a drive-less `file:///…`
|
|
82
|
+
// URL, whereas the pathname (`/repo/mcp_server/…`) is available on every
|
|
83
|
+
// platform. This keeps resolution deterministic across POSIX and Windows.
|
|
84
|
+
let pathname;
|
|
85
|
+
try {
|
|
86
|
+
pathname = decodeURIComponent(new URL(moduleUrl).pathname);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
// A real Windows `import.meta.url` is `file:///C:/…`, whose pathname is
|
|
92
|
+
// `/C:/…`; strip the leading slash so the drive letter leads (Node accepts
|
|
93
|
+
// forward slashes on Windows). Drive-less POSIX pathnames are left unchanged.
|
|
94
|
+
if (/^\/[A-Za-z]:/.test(pathname)) {
|
|
95
|
+
pathname = pathname.slice(1);
|
|
96
|
+
}
|
|
97
|
+
// Split on both separators so a Windows-materialized path (backslashes) and a
|
|
98
|
+
// posix path both segment identically.
|
|
99
|
+
const segments = pathname.split(/[\\/]/);
|
|
100
|
+
let markerIndex = -1;
|
|
101
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
102
|
+
if (segments[i] === "src" || segments[i] === "build") {
|
|
103
|
+
markerIndex = i;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (markerIndex <= 0)
|
|
108
|
+
return null;
|
|
109
|
+
return segments.slice(0, markerIndex).join("/");
|
|
110
|
+
}
|
|
111
|
+
/** Manual basename that tolerates either path separator. */
|
|
112
|
+
function basenameAnySep(filePath) {
|
|
113
|
+
const segments = filePath.split(/[\\/]/);
|
|
114
|
+
return segments[segments.length - 1] ?? "";
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Resolve how the worker MCP shim should be launched, preferring the conductor's
|
|
118
|
+
* own on-disk build by absolute path and falling back to a resolvable npm channel.
|
|
119
|
+
*
|
|
120
|
+
* Resolution order (never uses `process.cwd()`):
|
|
121
|
+
* 1. `<packageRoot>/build/index.js` derived from `deps.moduleUrl`.
|
|
122
|
+
* 2. `deps.argv1` when it is absolute, exists, and its basename is `index.js`.
|
|
123
|
+
* 3. npm-channel fallback (`deps.npmPackageSpec ?? DEFAULT_MCP_SERVER_NPM_CHANNEL_SPEC`).
|
|
124
|
+
*
|
|
125
|
+
* NOTE (scope boundary): this ticket changes WHICH server file is invoked, not
|
|
126
|
+
* WHICH Node runtime runs it. `nodeExecutable` defaults to `"node"` (NOT
|
|
127
|
+
* `process.execPath`) to preserve today's worker runtime selection; Ticket 3 (P2)
|
|
128
|
+
* can later inject `CONDUCTOR_NODE_PATH` into this single field.
|
|
129
|
+
*/
|
|
130
|
+
export function resolveMcpShimInvocationForRuntime(deps) {
|
|
131
|
+
const nodeExecutable = deps.nodeExecutable ?? "node";
|
|
132
|
+
const packageRoot = resolvePackageRootFromModuleUrl(deps.moduleUrl);
|
|
133
|
+
if (packageRoot) {
|
|
134
|
+
const candidate = `${packageRoot}/build/index.js`;
|
|
135
|
+
if (deps.fileExists(candidate)) {
|
|
136
|
+
return { form: "absolute-build-path", nodeExecutable, serverEntryPath: candidate };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const argv1 = deps.argv1;
|
|
140
|
+
if (typeof argv1 === "string" &&
|
|
141
|
+
argv1.length > 0 &&
|
|
142
|
+
path.isAbsolute(argv1) &&
|
|
143
|
+
basenameAnySep(argv1) === "index.js" &&
|
|
144
|
+
deps.fileExists(argv1)) {
|
|
145
|
+
return { form: "absolute-build-path", nodeExecutable, serverEntryPath: argv1 };
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
form: "npm-channel",
|
|
149
|
+
command: "npx",
|
|
150
|
+
packageSpec: deps.npmPackageSpec ?? DEFAULT_MCP_SERVER_NPM_CHANNEL_SPEC,
|
|
151
|
+
};
|
|
152
|
+
}
|