@bridge_gpt/mcp-server 0.1.17 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +334 -196
- package/build/agent-capabilities/cli.js +152 -0
- package/build/agent-capabilities/default-deps.js +45 -0
- package/build/agent-capabilities/probe-context.js +111 -0
- package/build/agent-capabilities/probes.js +278 -0
- package/build/agent-capabilities/reporter.js +50 -0
- package/build/agent-capabilities/runner.js +56 -0
- package/build/agent-capabilities/types.js +10 -0
- package/build/agent-launchers/claude.js +25 -17
- package/build/agent-launchers/cursor.js +65 -0
- package/build/agent-launchers/index.js +23 -8
- package/build/agent-registry.js +68 -0
- package/build/agents.generated.js +1 -1
- package/build/brainstorm-files.js +89 -0
- package/build/bridge-config.js +404 -0
- package/build/chain-orchestrator.js +247 -33
- package/build/command-catalog.js +376 -0
- package/build/commands.generated.js +10 -7
- package/build/credential-materialization.js +128 -0
- package/build/credential-store.js +232 -0
- package/build/decision-page-schema.js +39 -6
- package/build/decision-page-template.js +54 -18
- package/build/doctor.js +18 -2
- package/build/git-ignore-utils.js +63 -0
- package/build/index.js +1707 -557
- package/build/mcp-invoke.js +417 -0
- package/build/mcp-provisioning.js +342 -0
- package/build/mcp-registration-doctor.js +96 -0
- package/build/pipeline-orchestrator.js +9 -1
- package/build/pipelines.generated.js +5 -3
- package/build/schedule-run.js +440 -92
- package/build/schedule-store.js +41 -1
- package/build/scheduled-prompt.js +109 -0
- package/build/scheduler-backends/at-fallback.js +5 -10
- package/build/scheduler-backends/escaping.js +40 -10
- package/build/scheduler-backends/launchd.js +23 -14
- package/build/scheduler-backends/systemd-user.js +32 -19
- package/build/scheduler-backends/task-scheduler.js +8 -13
- package/build/start-tickets-prereqs.js +90 -1
- package/build/start-tickets.js +563 -42
- package/build/third-party-mcp-targets.js +75 -0
- package/build/version.generated.js +1 -1
- package/package.json +4 -3
- package/pipelines/full-automation.json +3 -1
- package/smoke-test/SMOKE-TEST.md +62 -17
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mcp-invoke` — internal worktree shim subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Resolves an MCP target's launch command + environment from an explicit
|
|
5
|
+
* absolute `--project-root` (NEVER from `process.cwd()`), then spawns the real
|
|
6
|
+
* MCP server as a child of the current Node process with inherited stdio and
|
|
7
|
+
* forwarded signals/exit codes. Secrets are passed to the child ONLY through its
|
|
8
|
+
* environment — never in argv, logs, or error messages.
|
|
9
|
+
*
|
|
10
|
+
* Two target families:
|
|
11
|
+
* - `bapi` — resolves Bridge API repo identity + `BAPI_API_KEY`, overlays the
|
|
12
|
+
* three `BAPI_*` values, and spawns this same Node script (the real Bridge
|
|
13
|
+
* API MCP server).
|
|
14
|
+
* - any Tier-2 target declared in `.bridge/config` (e.g. `sfcc`) — reads the
|
|
15
|
+
* manifest entry, resolves the target's required env overlay atomically from
|
|
16
|
+
* the secret store, and spawns the manifest `command`/`args`.
|
|
17
|
+
*
|
|
18
|
+
* This file is the highest-risk surface of the worktree-credentials work: the
|
|
19
|
+
* stdio passthrough plus the signal/exit-code contract is covered exhaustively
|
|
20
|
+
* by its unit tests.
|
|
21
|
+
*/
|
|
22
|
+
import { spawn, execFile } from "child_process";
|
|
23
|
+
import { stat, readFile } from "fs/promises";
|
|
24
|
+
import path from "path";
|
|
25
|
+
import os from "os";
|
|
26
|
+
import { resolveRepoNameForProjectRoot, readBridgeConfig, validateMcpTarget, } from "./bridge-config.js";
|
|
27
|
+
import { resolveBapiCredentials } from "./credential-store.js";
|
|
28
|
+
import { getThirdPartyTargetDefinition, resolveThirdPartyTargetEnv, validateThirdPartyTargetManifestEntry, } from "./third-party-mcp-targets.js";
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Usage / argument parsing
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
export function getMcpInvokeUsage() {
|
|
33
|
+
return [
|
|
34
|
+
"Usage:",
|
|
35
|
+
" npx -y @bridge_gpt/mcp-server mcp-invoke --target <target> --project-root <ABS_WORKTREE_PATH>",
|
|
36
|
+
"",
|
|
37
|
+
"Internal worktree shim: resolves the target's launch command and credentials",
|
|
38
|
+
"from the given project root, then spawns the real MCP server over stdio.",
|
|
39
|
+
"",
|
|
40
|
+
"Flags:",
|
|
41
|
+
" --target <target> MCP target to launch. 'bapi' launches the Bridge",
|
|
42
|
+
" API server; a configured third-party target from",
|
|
43
|
+
" .bridge/config (e.g. sfcc) launches that server.",
|
|
44
|
+
" --project-root <ABS_PATH> Absolute path to the worktree (required)",
|
|
45
|
+
" -h, --help Show this help",
|
|
46
|
+
].join("\n");
|
|
47
|
+
}
|
|
48
|
+
const KNOWN_FLAGS = new Set(["--target", "--project-root"]);
|
|
49
|
+
/**
|
|
50
|
+
* Parse `--target` and `--project-root` (split or `=` form). Rejects unknown
|
|
51
|
+
* flags, positional arguments, missing values, and duplicates. Requires a safe
|
|
52
|
+
* non-empty `--target` identifier and a host-platform-absolute `--project-root`.
|
|
53
|
+
*/
|
|
54
|
+
export function parseMcpInvokeArgs(argv) {
|
|
55
|
+
let target;
|
|
56
|
+
let projectRoot;
|
|
57
|
+
for (let i = 0; i < argv.length; i++) {
|
|
58
|
+
const token = argv[i];
|
|
59
|
+
if (token === "-h" || token === "--help") {
|
|
60
|
+
return { status: "help", usage: getMcpInvokeUsage() };
|
|
61
|
+
}
|
|
62
|
+
let flag = token;
|
|
63
|
+
let value;
|
|
64
|
+
const eq = token.indexOf("=");
|
|
65
|
+
if (token.startsWith("--") && eq !== -1) {
|
|
66
|
+
flag = token.slice(0, eq);
|
|
67
|
+
value = token.slice(eq + 1);
|
|
68
|
+
}
|
|
69
|
+
if (!KNOWN_FLAGS.has(flag)) {
|
|
70
|
+
if (token.startsWith("-")) {
|
|
71
|
+
return { status: "error", message: `Unknown flag: ${flag}` };
|
|
72
|
+
}
|
|
73
|
+
return { status: "error", message: `Unexpected positional argument: ${token}` };
|
|
74
|
+
}
|
|
75
|
+
if (value === undefined) {
|
|
76
|
+
const next = argv[i + 1];
|
|
77
|
+
if (next === undefined || next.startsWith("-")) {
|
|
78
|
+
return { status: "error", message: `Missing value for ${flag}` };
|
|
79
|
+
}
|
|
80
|
+
value = next;
|
|
81
|
+
i++;
|
|
82
|
+
}
|
|
83
|
+
if (flag === "--target") {
|
|
84
|
+
if (target !== undefined) {
|
|
85
|
+
return { status: "error", message: "Duplicate --target flag" };
|
|
86
|
+
}
|
|
87
|
+
target = value;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
if (projectRoot !== undefined) {
|
|
91
|
+
return { status: "error", message: "Duplicate --project-root flag" };
|
|
92
|
+
}
|
|
93
|
+
projectRoot = value;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (target === undefined) {
|
|
97
|
+
return { status: "error", message: "Missing required --target flag" };
|
|
98
|
+
}
|
|
99
|
+
const targetValidation = validateMcpTarget(target);
|
|
100
|
+
if (!targetValidation.ok) {
|
|
101
|
+
return { status: "error", message: `Invalid --target: ${targetValidation.error}` };
|
|
102
|
+
}
|
|
103
|
+
if (projectRoot === undefined) {
|
|
104
|
+
return { status: "error", message: "Missing required --project-root flag" };
|
|
105
|
+
}
|
|
106
|
+
if (!path.isAbsolute(projectRoot)) {
|
|
107
|
+
return { status: "error", message: "--project-root must be an absolute path" };
|
|
108
|
+
}
|
|
109
|
+
return { status: "ok", target: targetValidation.value, projectRoot };
|
|
110
|
+
}
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Validation, env construction, signals
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
/** Fail fast when `--project-root` does not exist or is not a directory. */
|
|
115
|
+
export async function validateProjectRootDirectory(projectRoot, statFn) {
|
|
116
|
+
try {
|
|
117
|
+
const result = await statFn(projectRoot);
|
|
118
|
+
if (!result.isDirectory()) {
|
|
119
|
+
return { ok: false, error: `--project-root is not a directory: ${projectRoot}` };
|
|
120
|
+
}
|
|
121
|
+
return { ok: true };
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return { ok: false, error: `--project-root does not exist: ${projectRoot}` };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Build the child environment: start from the parent env, overlay an arbitrary
|
|
129
|
+
* set of resolved values. The parent env object is never mutated; unrelated
|
|
130
|
+
* parent values (`BAPI_BASE_URL`, `BAPI_DOCS_DIR`, proxy variables, Node env)
|
|
131
|
+
* pass through unchanged. Overlay values are typically secrets and must only
|
|
132
|
+
* ever reach the child through this env object — never argv or logs.
|
|
133
|
+
*/
|
|
134
|
+
export function buildChildEnv(parentEnv, overlay) {
|
|
135
|
+
return { ...parentEnv, ...overlay };
|
|
136
|
+
}
|
|
137
|
+
/** Map a terminating signal to a conventional `128 + signum` exit code. */
|
|
138
|
+
export function signalExitCode(signal) {
|
|
139
|
+
if (signal === "SIGINT")
|
|
140
|
+
return 130;
|
|
141
|
+
if (signal === "SIGTERM")
|
|
142
|
+
return 143;
|
|
143
|
+
const signals = os.constants.signals;
|
|
144
|
+
const num = signals[signal];
|
|
145
|
+
if (typeof num === "number")
|
|
146
|
+
return 128 + num;
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// Child server spawn
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
/**
|
|
153
|
+
* Spawn an MCP server child with the given command/args, inherited stdio, and
|
|
154
|
+
* the merged child env. SIGINT/SIGTERM received by the shim are forwarded to the
|
|
155
|
+
* child; the returned promise resolves once the child closes, propagating its
|
|
156
|
+
* numeric exit code (or the signal exit code). Signal handlers are registered
|
|
157
|
+
* after spawn and removed exactly once on close.
|
|
158
|
+
*/
|
|
159
|
+
export function spawnMcpCommand(deps) {
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
let child;
|
|
162
|
+
try {
|
|
163
|
+
child = deps.spawn(deps.command, deps.args, {
|
|
164
|
+
stdio: "inherit",
|
|
165
|
+
env: deps.env,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
reject(err);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const signals = ["SIGINT", "SIGTERM"];
|
|
173
|
+
const handlers = {};
|
|
174
|
+
let cleaned = false;
|
|
175
|
+
const cleanup = () => {
|
|
176
|
+
if (cleaned)
|
|
177
|
+
return;
|
|
178
|
+
cleaned = true;
|
|
179
|
+
for (const sig of signals)
|
|
180
|
+
deps.offSignal(sig, handlers[sig]);
|
|
181
|
+
};
|
|
182
|
+
for (const sig of signals) {
|
|
183
|
+
const handler = () => {
|
|
184
|
+
try {
|
|
185
|
+
child.kill(sig);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
/* best-effort forward; the close handler still resolves */
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
handlers[sig] = handler;
|
|
192
|
+
deps.onSignal(sig, handler);
|
|
193
|
+
}
|
|
194
|
+
child.on("error", (err) => {
|
|
195
|
+
cleanup();
|
|
196
|
+
reject(err);
|
|
197
|
+
});
|
|
198
|
+
child.on("close", (...args) => {
|
|
199
|
+
cleanup();
|
|
200
|
+
const code = args[0];
|
|
201
|
+
const signal = args[1];
|
|
202
|
+
if (typeof code === "number") {
|
|
203
|
+
resolve(code);
|
|
204
|
+
}
|
|
205
|
+
else if (signal) {
|
|
206
|
+
resolve(signalExitCode(signal));
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
resolve(0);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Back-compat wrapper: spawn the real Bridge API MCP server as
|
|
216
|
+
* `node <thisScript>` (no `mcp-invoke` subcommand args, so the child enters the
|
|
217
|
+
* normal no-subcommand startup path). Delegates to {@link spawnMcpCommand}.
|
|
218
|
+
*/
|
|
219
|
+
export function spawnRealMcpServer(deps) {
|
|
220
|
+
return spawnMcpCommand({
|
|
221
|
+
spawn: deps.spawn,
|
|
222
|
+
command: deps.execPath,
|
|
223
|
+
args: [deps.scriptPath],
|
|
224
|
+
env: deps.env,
|
|
225
|
+
onSignal: deps.onSignal,
|
|
226
|
+
offSignal: deps.offSignal,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
// Default production runners (used when overrides are not supplied)
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
function defaultRunCommand(file, args, options) {
|
|
233
|
+
return new Promise((resolve) => {
|
|
234
|
+
execFile(file, args, { cwd: options?.cwd }, (err, stdout, stderr) => {
|
|
235
|
+
if (err) {
|
|
236
|
+
const code = typeof err.code === "number"
|
|
237
|
+
? (err.code)
|
|
238
|
+
: 1;
|
|
239
|
+
resolve({
|
|
240
|
+
stdout: stdout?.toString() ?? "",
|
|
241
|
+
stderr: stderr?.toString() ?? err.message,
|
|
242
|
+
exitCode: code,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
resolve({ stdout: stdout.toString(), stderr: stderr.toString(), exitCode: 0 });
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Resolve the Bridge API invocation: derive repo identity, resolve the API key
|
|
253
|
+
* (env→store), and overlay the three `BAPI_*` values onto the parent env. The
|
|
254
|
+
* spawned command is `process.execPath` with `[scriptPath]` (no subcommand args
|
|
255
|
+
* — the child runs the normal server). Secret-free errors on any failure.
|
|
256
|
+
*/
|
|
257
|
+
export async function resolveBapiInvocation(projectRoot, deps) {
|
|
258
|
+
const repoResult = await deps.resolveRepoName(projectRoot);
|
|
259
|
+
if (!repoResult.ok) {
|
|
260
|
+
return { ok: false, error: `failed to resolve repo identity: ${repoResult.error}` };
|
|
261
|
+
}
|
|
262
|
+
const repoName = repoResult.value;
|
|
263
|
+
const credResult = await deps.resolveCredentials(repoName, projectRoot);
|
|
264
|
+
if (!credResult.ok) {
|
|
265
|
+
return { ok: false, error: `failed to resolve credentials: ${credResult.error}` };
|
|
266
|
+
}
|
|
267
|
+
const env = buildChildEnv(deps.env, {
|
|
268
|
+
BAPI_API_KEY: credResult.credentials.apiKey,
|
|
269
|
+
BAPI_PROJECT_ROOT: projectRoot,
|
|
270
|
+
BAPI_REPO_NAME: repoName,
|
|
271
|
+
});
|
|
272
|
+
return {
|
|
273
|
+
ok: true,
|
|
274
|
+
invocation: { command: deps.execPath, args: [deps.scriptPath], env },
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Resolve a Tier-2 third-party invocation: read `.bridge/config`, locate the
|
|
279
|
+
* requested target entry, resolve the target's required env overlay atomically
|
|
280
|
+
* from the secret store, and return the manifest `command`/`args` plus the
|
|
281
|
+
* overlaid child env. Credentials never enter argv. Secret-free errors.
|
|
282
|
+
*/
|
|
283
|
+
export async function resolveThirdPartyInvocation(target, projectRoot, deps) {
|
|
284
|
+
const definition = deps.getTargetDefinition(target);
|
|
285
|
+
if (!definition) {
|
|
286
|
+
return {
|
|
287
|
+
ok: false,
|
|
288
|
+
error: `unsupported MCP target '${target}'; not a known third-party target`,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
const read = await deps.readBridgeConfig(projectRoot);
|
|
292
|
+
if (!read.ok) {
|
|
293
|
+
if (read.kind === "missing") {
|
|
294
|
+
return { ok: false, error: `target '${target}' requires a .bridge/config; none was found` };
|
|
295
|
+
}
|
|
296
|
+
return { ok: false, error: `unable to read .bridge/config for target '${target}'` };
|
|
297
|
+
}
|
|
298
|
+
const entry = read.manifest.mcp.find((m) => m.target === target);
|
|
299
|
+
if (!entry) {
|
|
300
|
+
return {
|
|
301
|
+
ok: false,
|
|
302
|
+
error: `target '${target}' is not declared in .bridge/config`,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
const entryValidation = validateThirdPartyTargetManifestEntry(entry);
|
|
306
|
+
if (!entryValidation.ok) {
|
|
307
|
+
return { ok: false, error: entryValidation.error };
|
|
308
|
+
}
|
|
309
|
+
// After validation these are guaranteed present.
|
|
310
|
+
const command = entry.command;
|
|
311
|
+
const args = entry.args;
|
|
312
|
+
const secretBundle = entry.secretBundle;
|
|
313
|
+
const envResult = await deps.resolveTargetEnv(definition, secretBundle);
|
|
314
|
+
if (!envResult.ok) {
|
|
315
|
+
return { ok: false, error: `failed to resolve credentials for '${target}': ${envResult.error}` };
|
|
316
|
+
}
|
|
317
|
+
const env = buildChildEnv(deps.env, envResult.env);
|
|
318
|
+
return { ok: true, invocation: { command, args, env } };
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Run the `mcp-invoke` subcommand. Returns a process exit code (never calls
|
|
322
|
+
* `process.exit`). Parses args BEFORE any validation/resolution; validates the
|
|
323
|
+
* project root; dispatches `bapi` to {@link resolveBapiInvocation} and any other
|
|
324
|
+
* target to {@link resolveThirdPartyInvocation}; then spawns. Any failure prints
|
|
325
|
+
* an actionable, secret-free message to stderr and returns non-zero without
|
|
326
|
+
* spawning.
|
|
327
|
+
*/
|
|
328
|
+
export async function runMcpInvokeCli(argv, overrides = {}) {
|
|
329
|
+
const log = overrides.log ?? ((m) => process.stdout.write(`${m}\n`));
|
|
330
|
+
const stderr = overrides.stderr ?? ((m) => process.stderr.write(`${m}\n`));
|
|
331
|
+
const env = overrides.env ?? process.env;
|
|
332
|
+
const parsed = parseMcpInvokeArgs(argv);
|
|
333
|
+
if (parsed.status === "help") {
|
|
334
|
+
log(parsed.usage);
|
|
335
|
+
return 0;
|
|
336
|
+
}
|
|
337
|
+
if (parsed.status === "error") {
|
|
338
|
+
stderr(`Error: ${parsed.message}`);
|
|
339
|
+
stderr("");
|
|
340
|
+
stderr(getMcpInvokeUsage());
|
|
341
|
+
return 1;
|
|
342
|
+
}
|
|
343
|
+
const { target, projectRoot } = parsed;
|
|
344
|
+
const statFn = overrides.stat ?? ((p) => stat(p));
|
|
345
|
+
const dirCheck = await validateProjectRootDirectory(projectRoot, statFn);
|
|
346
|
+
if (!dirCheck.ok) {
|
|
347
|
+
stderr(`Error: ${dirCheck.error}`);
|
|
348
|
+
return 1;
|
|
349
|
+
}
|
|
350
|
+
const credentialDeps = {
|
|
351
|
+
env,
|
|
352
|
+
homedir: os.homedir,
|
|
353
|
+
platform: process.platform,
|
|
354
|
+
readFile: (p) => readFile(p, "utf-8"),
|
|
355
|
+
stat: (p) => stat(p),
|
|
356
|
+
stderr,
|
|
357
|
+
};
|
|
358
|
+
if (target === "bapi") {
|
|
359
|
+
const resolveRepoName = overrides.resolveRepoName ??
|
|
360
|
+
((pr) => resolveRepoNameForProjectRoot(pr, {
|
|
361
|
+
readFile: (p) => readFile(p, "utf-8"),
|
|
362
|
+
runCommand: defaultRunCommand,
|
|
363
|
+
}));
|
|
364
|
+
const resolveCredentials = overrides.resolveCredentials ??
|
|
365
|
+
((rn) => resolveBapiCredentials(rn, credentialDeps));
|
|
366
|
+
const resolved = await resolveBapiInvocation(projectRoot, {
|
|
367
|
+
env,
|
|
368
|
+
execPath: process.execPath,
|
|
369
|
+
scriptPath: process.argv[1],
|
|
370
|
+
resolveRepoName,
|
|
371
|
+
resolveCredentials,
|
|
372
|
+
});
|
|
373
|
+
if (!resolved.ok) {
|
|
374
|
+
stderr(`Error: ${resolved.error}`);
|
|
375
|
+
return 1;
|
|
376
|
+
}
|
|
377
|
+
return spawnInvocation(resolved.invocation, overrides);
|
|
378
|
+
}
|
|
379
|
+
// Tier-2 third-party target.
|
|
380
|
+
const readConfig = overrides.readBridgeConfig ??
|
|
381
|
+
((pr) => readBridgeConfig(pr, { readFile: (p) => readFile(p, "utf-8") }));
|
|
382
|
+
const getTargetDefinition = overrides.getTargetDefinition ?? getThirdPartyTargetDefinition;
|
|
383
|
+
const resolveTargetEnv = overrides.resolveThirdPartyEnv ??
|
|
384
|
+
((definition, secretBundle) => resolveThirdPartyTargetEnv(definition, secretBundle, credentialDeps));
|
|
385
|
+
const resolved = await resolveThirdPartyInvocation(target, projectRoot, {
|
|
386
|
+
env,
|
|
387
|
+
readBridgeConfig: readConfig,
|
|
388
|
+
getTargetDefinition,
|
|
389
|
+
resolveTargetEnv,
|
|
390
|
+
});
|
|
391
|
+
if (!resolved.ok) {
|
|
392
|
+
stderr(`Error: ${resolved.error}`);
|
|
393
|
+
return 1;
|
|
394
|
+
}
|
|
395
|
+
return spawnInvocation(resolved.invocation, overrides);
|
|
396
|
+
}
|
|
397
|
+
/** Spawn a resolved invocation, honoring the legacy and generic spawn overrides. */
|
|
398
|
+
function spawnInvocation(invocation, overrides) {
|
|
399
|
+
if (overrides.spawnMcpCommand) {
|
|
400
|
+
return overrides.spawnMcpCommand(invocation);
|
|
401
|
+
}
|
|
402
|
+
if (overrides.spawnRealMcpServer) {
|
|
403
|
+
return overrides.spawnRealMcpServer(invocation.env);
|
|
404
|
+
}
|
|
405
|
+
return spawnMcpCommand({
|
|
406
|
+
spawn: spawn,
|
|
407
|
+
command: invocation.command,
|
|
408
|
+
args: invocation.args,
|
|
409
|
+
env: invocation.env,
|
|
410
|
+
onSignal: (s, h) => {
|
|
411
|
+
process.on(s, h);
|
|
412
|
+
},
|
|
413
|
+
offSignal: (s, h) => {
|
|
414
|
+
process.off(s, h);
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
}
|