zelari-code 2.20.0 → 2.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/main.bundled.js +401 -68
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/main.js +22 -5
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/mcp/httpTransport.js +184 -0
- package/dist/cli/mcp/httpTransport.js.map +1 -0
- package/dist/cli/mcp/mcpClient.js +153 -54
- package/dist/cli/mcp/mcpClient.js.map +1 -1
- package/dist/cli/mcp/mcpConfigIo.js +26 -5
- package/dist/cli/mcp/mcpConfigIo.js.map +1 -1
- package/dist/cli/mcp/mcpManager.js +51 -3
- package/dist/cli/mcp/mcpManager.js.map +1 -1
- package/dist/cli/mcp/mcpPresets.js +42 -1
- package/dist/cli/mcp/mcpPresets.js.map +1 -1
- package/dist/cli/skillConfigIo.js +1 -0
- package/dist/cli/skillConfigIo.js.map +1 -1
- package/package.json +2 -2
package/dist/cli/main.bundled.js
CHANGED
|
@@ -55304,39 +55304,224 @@ var init_toolRegistry2 = __esm({
|
|
|
55304
55304
|
}
|
|
55305
55305
|
});
|
|
55306
55306
|
|
|
55307
|
+
// src/cli/mcp/httpTransport.ts
|
|
55308
|
+
var SSE_DATA_RE, ABORT_GRACE_MS, McpHttpTransport;
|
|
55309
|
+
var init_httpTransport = __esm({
|
|
55310
|
+
"src/cli/mcp/httpTransport.ts"() {
|
|
55311
|
+
"use strict";
|
|
55312
|
+
SSE_DATA_RE = /^data:\s?(.*)$/;
|
|
55313
|
+
ABORT_GRACE_MS = 250;
|
|
55314
|
+
McpHttpTransport = class {
|
|
55315
|
+
constructor(opts) {
|
|
55316
|
+
this.opts = opts;
|
|
55317
|
+
}
|
|
55318
|
+
sessionId = null;
|
|
55319
|
+
closed = false;
|
|
55320
|
+
reinit = null;
|
|
55321
|
+
controllers = /* @__PURE__ */ new Set();
|
|
55322
|
+
get hasSession() {
|
|
55323
|
+
return this.sessionId !== null;
|
|
55324
|
+
}
|
|
55325
|
+
/**
|
|
55326
|
+
* Deliver one JSON-RPC message. Resolves once the response (if any) has
|
|
55327
|
+
* been fed to onMessage. Transport-level failures are converted into
|
|
55328
|
+
* JSON-RPC error responses so the client's pending map rejects cleanly
|
|
55329
|
+
* through the same pump used for stdio.
|
|
55330
|
+
*/
|
|
55331
|
+
async send(msg, timeoutMs2) {
|
|
55332
|
+
if (this.closed) throw new Error(`[mcp:${this.opts.serverName}] transport closed`);
|
|
55333
|
+
try {
|
|
55334
|
+
await this.post(msg, timeoutMs2, false);
|
|
55335
|
+
} catch (err) {
|
|
55336
|
+
if (msg.id !== void 0) {
|
|
55337
|
+
this.opts.onMessage({
|
|
55338
|
+
jsonrpc: "2.0",
|
|
55339
|
+
id: msg.id,
|
|
55340
|
+
error: {
|
|
55341
|
+
code: -32e3,
|
|
55342
|
+
message: err instanceof Error ? err.message : String(err)
|
|
55343
|
+
}
|
|
55344
|
+
});
|
|
55345
|
+
}
|
|
55346
|
+
}
|
|
55347
|
+
}
|
|
55348
|
+
/** Best-effort session teardown (HTTP DELETE), then abort in-flight POSTs. */
|
|
55349
|
+
close() {
|
|
55350
|
+
this.closed = true;
|
|
55351
|
+
for (const ac of this.controllers) ac.abort();
|
|
55352
|
+
this.controllers.clear();
|
|
55353
|
+
const sid = this.sessionId;
|
|
55354
|
+
this.sessionId = null;
|
|
55355
|
+
if (!sid) return;
|
|
55356
|
+
const headers2 = {
|
|
55357
|
+
...this.opts.headers ?? {},
|
|
55358
|
+
"mcp-session-id": sid
|
|
55359
|
+
};
|
|
55360
|
+
void fetch(this.opts.url, { method: "DELETE", headers: headers2 }).catch(() => {
|
|
55361
|
+
});
|
|
55362
|
+
}
|
|
55363
|
+
// ── internals ────────────────────────────────────────────────────────
|
|
55364
|
+
async post(msg, timeoutMs2, replayed) {
|
|
55365
|
+
const ac = new AbortController();
|
|
55366
|
+
this.controllers.add(ac);
|
|
55367
|
+
const timer = setTimeout(() => ac.abort(), timeoutMs2 + ABORT_GRACE_MS);
|
|
55368
|
+
const hadSession = this.sessionId !== null;
|
|
55369
|
+
try {
|
|
55370
|
+
const headers2 = {
|
|
55371
|
+
"content-type": "application/json",
|
|
55372
|
+
accept: "application/json, text/event-stream",
|
|
55373
|
+
...this.opts.headers ?? {}
|
|
55374
|
+
};
|
|
55375
|
+
if (this.sessionId) headers2["mcp-session-id"] = this.sessionId;
|
|
55376
|
+
const res = await fetch(this.opts.url, {
|
|
55377
|
+
method: "POST",
|
|
55378
|
+
headers: headers2,
|
|
55379
|
+
signal: ac.signal,
|
|
55380
|
+
body: JSON.stringify({ jsonrpc: "2.0", ...msg })
|
|
55381
|
+
});
|
|
55382
|
+
const sid = res.headers.get("mcp-session-id");
|
|
55383
|
+
if (sid) this.sessionId = sid;
|
|
55384
|
+
if (res.status === 404 && hadSession && !replayed && msg.id !== void 0) {
|
|
55385
|
+
this.sessionId = null;
|
|
55386
|
+
await this.ensureSession();
|
|
55387
|
+
return this.post(msg, timeoutMs2, true);
|
|
55388
|
+
}
|
|
55389
|
+
if (!res.ok && res.status !== 202) {
|
|
55390
|
+
throw new Error(`HTTP ${res.status} ${res.statusText}`.trim());
|
|
55391
|
+
}
|
|
55392
|
+
if (msg.id === void 0) {
|
|
55393
|
+
await res.body?.cancel();
|
|
55394
|
+
return;
|
|
55395
|
+
}
|
|
55396
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
55397
|
+
if (contentType.includes("text/event-stream")) {
|
|
55398
|
+
await this.readSse(res);
|
|
55399
|
+
} else {
|
|
55400
|
+
const body = await res.text();
|
|
55401
|
+
if (body) this.opts.onMessage(JSON.parse(body));
|
|
55402
|
+
}
|
|
55403
|
+
} finally {
|
|
55404
|
+
clearTimeout(timer);
|
|
55405
|
+
this.controllers.delete(ac);
|
|
55406
|
+
}
|
|
55407
|
+
}
|
|
55408
|
+
/** Dedupe concurrent session-loss recoveries into one handshake. */
|
|
55409
|
+
async ensureSession() {
|
|
55410
|
+
if (!this.reinit) {
|
|
55411
|
+
this.reinit = this.opts.onSessionLost().finally(() => {
|
|
55412
|
+
this.reinit = null;
|
|
55413
|
+
}).catch(() => {
|
|
55414
|
+
});
|
|
55415
|
+
}
|
|
55416
|
+
await this.reinit;
|
|
55417
|
+
}
|
|
55418
|
+
/** Minimal SSE reader: dispatch complete `data:` events to onMessage. */
|
|
55419
|
+
async readSse(res) {
|
|
55420
|
+
const reader = res.body?.getReader();
|
|
55421
|
+
if (!reader) return;
|
|
55422
|
+
const decoder = new TextDecoder();
|
|
55423
|
+
let buf = "";
|
|
55424
|
+
let data = "";
|
|
55425
|
+
const dispatch = () => {
|
|
55426
|
+
const payload = data.trim();
|
|
55427
|
+
data = "";
|
|
55428
|
+
if (!payload) return;
|
|
55429
|
+
try {
|
|
55430
|
+
this.opts.onMessage(JSON.parse(payload));
|
|
55431
|
+
} catch {
|
|
55432
|
+
}
|
|
55433
|
+
};
|
|
55434
|
+
for (; ; ) {
|
|
55435
|
+
const { done, value } = await reader.read();
|
|
55436
|
+
if (done) break;
|
|
55437
|
+
buf += decoder.decode(value, { stream: true });
|
|
55438
|
+
let nl;
|
|
55439
|
+
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
55440
|
+
const line = buf.slice(0, nl).replace(/\r$/, "");
|
|
55441
|
+
buf = buf.slice(nl + 1);
|
|
55442
|
+
if (line === "") {
|
|
55443
|
+
dispatch();
|
|
55444
|
+
continue;
|
|
55445
|
+
}
|
|
55446
|
+
const m = SSE_DATA_RE.exec(line);
|
|
55447
|
+
if (m) data += (data ? "\n" : "") + m[1];
|
|
55448
|
+
}
|
|
55449
|
+
}
|
|
55450
|
+
dispatch();
|
|
55451
|
+
}
|
|
55452
|
+
};
|
|
55453
|
+
}
|
|
55454
|
+
});
|
|
55455
|
+
|
|
55307
55456
|
// src/cli/mcp/mcpClient.ts
|
|
55308
55457
|
import { spawn as spawn14 } from "node:child_process";
|
|
55309
|
-
var DEFAULT_REQUEST_TIMEOUT_MS, INIT_TIMEOUT_MS, MCP_PROTOCOL_VERSION, McpClient;
|
|
55458
|
+
var DEFAULT_REQUEST_TIMEOUT_MS, INIT_TIMEOUT_MS, MAX_LIST_PAGES, MCP_PROTOCOL_VERSION, McpClient;
|
|
55310
55459
|
var init_mcpClient = __esm({
|
|
55311
55460
|
"src/cli/mcp/mcpClient.ts"() {
|
|
55312
55461
|
"use strict";
|
|
55313
55462
|
init_cmdline();
|
|
55314
55463
|
init_updater();
|
|
55464
|
+
init_httpTransport();
|
|
55315
55465
|
DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
55316
55466
|
INIT_TIMEOUT_MS = 15e3;
|
|
55467
|
+
MAX_LIST_PAGES = 50;
|
|
55317
55468
|
MCP_PROTOCOL_VERSION = "2025-03-26";
|
|
55318
55469
|
McpClient = class {
|
|
55319
55470
|
constructor(serverName, config2) {
|
|
55320
55471
|
this.serverName = serverName;
|
|
55321
55472
|
this.config = config2;
|
|
55473
|
+
this.defaultTimeoutMs = config2.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
55474
|
+
this.serial = config2.serial ?? this.transportKind() === "http";
|
|
55322
55475
|
}
|
|
55323
55476
|
child = null;
|
|
55477
|
+
transport = null;
|
|
55324
55478
|
nextId = 1;
|
|
55325
55479
|
pending = /* @__PURE__ */ new Map();
|
|
55326
55480
|
stdoutBuffer = "";
|
|
55327
55481
|
closed = false;
|
|
55328
|
-
/**
|
|
55482
|
+
/** Tail of the serial queue (config.serial); requests run one at a time. */
|
|
55483
|
+
queueTail = Promise.resolve();
|
|
55484
|
+
/** True while the 404-recovery handshake runs: bypasses the serial queue
|
|
55485
|
+
* (the queued request that triggered the 404 is waiting on this handshake). */
|
|
55486
|
+
recovering = false;
|
|
55487
|
+
serial;
|
|
55488
|
+
defaultTimeoutMs;
|
|
55489
|
+
transportKind() {
|
|
55490
|
+
if (this.config.type) return this.config.type;
|
|
55491
|
+
return this.config.url && !this.config.command ? "http" : "stdio";
|
|
55492
|
+
}
|
|
55493
|
+
/** Connect (spawn / HTTP session) and run the MCP initialize handshake. */
|
|
55329
55494
|
async start() {
|
|
55330
|
-
if (this.child) return;
|
|
55495
|
+
if (this.child || this.transport) return;
|
|
55496
|
+
if (this.transportKind() === "http") {
|
|
55497
|
+
const url2 = this.config.url;
|
|
55498
|
+
if (!url2) {
|
|
55499
|
+
throw new Error(`[mcp:${this.serverName}] http server requires a url`);
|
|
55500
|
+
}
|
|
55501
|
+
this.transport = new McpHttpTransport({
|
|
55502
|
+
serverName: this.serverName,
|
|
55503
|
+
url: url2,
|
|
55504
|
+
// env may carry an explicit Authorization header for remote servers.
|
|
55505
|
+
headers: this.config.env?.AUTHORIZATION ? { Authorization: this.config.env.AUTHORIZATION } : void 0,
|
|
55506
|
+
onMessage: (msg) => this.handleMessage(msg),
|
|
55507
|
+
onSessionLost: () => this.handshake()
|
|
55508
|
+
});
|
|
55509
|
+
await this.handshake();
|
|
55510
|
+
return;
|
|
55511
|
+
}
|
|
55512
|
+
const command = this.config.command;
|
|
55513
|
+
if (!command) {
|
|
55514
|
+
throw new Error(`[mcp:${this.serverName}] stdio server requires a command`);
|
|
55515
|
+
}
|
|
55331
55516
|
const spawnOpts = {
|
|
55332
55517
|
stdio: ["pipe", "pipe", "pipe"],
|
|
55333
55518
|
env: { ...process.env, ...this.config.env ?? {} },
|
|
55334
55519
|
windowsHide: true
|
|
55335
55520
|
};
|
|
55336
|
-
const child = process.platform === "win32" ? spawn14(buildCmdLine(
|
|
55521
|
+
const child = process.platform === "win32" ? spawn14(buildCmdLine(command, this.config.args ?? []), {
|
|
55337
55522
|
...spawnOpts,
|
|
55338
55523
|
shell: true
|
|
55339
|
-
}) : spawn14(
|
|
55524
|
+
}) : spawn14(command, this.config.args ?? [], spawnOpts);
|
|
55340
55525
|
this.child = child;
|
|
55341
55526
|
child.stdout.setEncoding("utf8");
|
|
55342
55527
|
child.stdout.on("data", (chunk) => this.onStdout(chunk));
|
|
@@ -55355,37 +55540,60 @@ var init_mcpClient = __esm({
|
|
|
55355
55540
|
);
|
|
55356
55541
|
}
|
|
55357
55542
|
});
|
|
55358
|
-
await this.
|
|
55359
|
-
|
|
55360
|
-
|
|
55361
|
-
|
|
55362
|
-
|
|
55363
|
-
|
|
55364
|
-
|
|
55365
|
-
|
|
55366
|
-
|
|
55367
|
-
|
|
55543
|
+
await this.handshake();
|
|
55544
|
+
}
|
|
55545
|
+
async handshake() {
|
|
55546
|
+
const prev2 = this.recovering;
|
|
55547
|
+
this.recovering = true;
|
|
55548
|
+
try {
|
|
55549
|
+
await this.request(
|
|
55550
|
+
"initialize",
|
|
55551
|
+
{
|
|
55552
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
55553
|
+
capabilities: {},
|
|
55554
|
+
clientInfo: { name: "zelari-code", version: getCurrentVersion() }
|
|
55555
|
+
},
|
|
55556
|
+
INIT_TIMEOUT_MS
|
|
55557
|
+
);
|
|
55558
|
+
this.notify("notifications/initialized", {});
|
|
55559
|
+
} finally {
|
|
55560
|
+
this.recovering = prev2;
|
|
55561
|
+
}
|
|
55368
55562
|
}
|
|
55369
|
-
/**
|
|
55563
|
+
/**
|
|
55564
|
+
* Discover the server's tools. Follows `nextCursor` pagination so
|
|
55565
|
+
* large servers (hundreds of tools) are listed completely.
|
|
55566
|
+
*/
|
|
55370
55567
|
async listTools() {
|
|
55371
|
-
const
|
|
55372
|
-
|
|
55373
|
-
|
|
55374
|
-
|
|
55375
|
-
|
|
55376
|
-
|
|
55377
|
-
|
|
55378
|
-
|
|
55568
|
+
const tools = [];
|
|
55569
|
+
let cursor;
|
|
55570
|
+
for (let page = 0; page < MAX_LIST_PAGES; page++) {
|
|
55571
|
+
const res = await this.request(
|
|
55572
|
+
"tools/list",
|
|
55573
|
+
cursor ? { cursor } : {}
|
|
55574
|
+
);
|
|
55575
|
+
for (const t of res.tools ?? []) {
|
|
55576
|
+
if (!t.name) continue;
|
|
55577
|
+
tools.push({
|
|
55578
|
+
name: t.name,
|
|
55579
|
+
description: t.description ?? "",
|
|
55580
|
+
inputSchema: t.inputSchema ?? { type: "object", properties: {} }
|
|
55581
|
+
});
|
|
55582
|
+
}
|
|
55583
|
+
cursor = typeof res.nextCursor === "string" ? res.nextCursor : void 0;
|
|
55584
|
+
if (!cursor) break;
|
|
55585
|
+
}
|
|
55586
|
+
return tools;
|
|
55379
55587
|
}
|
|
55380
55588
|
/**
|
|
55381
55589
|
* Call a tool. Returns the concatenated text content; non-text content
|
|
55382
55590
|
* items are summarized by type. Throws when the server flags isError.
|
|
55383
55591
|
*/
|
|
55384
|
-
async callTool(name, args, timeoutMs2
|
|
55592
|
+
async callTool(name, args, timeoutMs2) {
|
|
55385
55593
|
const res = await this.request(
|
|
55386
55594
|
"tools/call",
|
|
55387
55595
|
{ name, arguments: args },
|
|
55388
|
-
timeoutMs2
|
|
55596
|
+
timeoutMs2 ?? this.defaultTimeoutMs
|
|
55389
55597
|
);
|
|
55390
55598
|
const text = (res.content ?? []).map(
|
|
55391
55599
|
(c) => c.type === "text" && typeof c.text === "string" ? c.text : `[${c.type ?? "unknown"} content]`
|
|
@@ -55394,20 +55602,39 @@ var init_mcpClient = __esm({
|
|
|
55394
55602
|
throw new Error(text || `tool "${name}" reported an error`);
|
|
55395
55603
|
return text;
|
|
55396
55604
|
}
|
|
55397
|
-
/** Terminate the server
|
|
55605
|
+
/** Terminate the server / session and reject all in-flight requests. */
|
|
55398
55606
|
close() {
|
|
55399
55607
|
this.closed = true;
|
|
55400
55608
|
this.failAll(new Error(`[mcp:${this.serverName}] client closed`));
|
|
55609
|
+
if (this.transport) {
|
|
55610
|
+
this.transport.close();
|
|
55611
|
+
this.transport = null;
|
|
55612
|
+
return;
|
|
55613
|
+
}
|
|
55401
55614
|
this.child?.kill();
|
|
55402
55615
|
this.child = null;
|
|
55403
55616
|
}
|
|
55404
|
-
// ── JSON-RPC plumbing
|
|
55405
|
-
request(method, params, timeoutMs2
|
|
55617
|
+
// ── JSON-RPC plumbing (shared by both transports) ────────────────────
|
|
55618
|
+
request(method, params, timeoutMs2) {
|
|
55619
|
+
const effective = timeoutMs2 ?? this.defaultTimeoutMs;
|
|
55620
|
+
if (!this.serial || this.recovering)
|
|
55621
|
+
return this.dispatch(method, params, effective);
|
|
55622
|
+
const run = this.queueTail.then(
|
|
55623
|
+
() => this.dispatch(method, params, effective),
|
|
55624
|
+
() => this.dispatch(method, params, effective)
|
|
55625
|
+
);
|
|
55626
|
+
this.queueTail = run.then(
|
|
55627
|
+
() => void 0,
|
|
55628
|
+
() => void 0
|
|
55629
|
+
);
|
|
55630
|
+
return run;
|
|
55631
|
+
}
|
|
55632
|
+
dispatch(method, params, timeoutMs2) {
|
|
55633
|
+
const transport = this.transport;
|
|
55406
55634
|
const child = this.child;
|
|
55407
|
-
if (!child)
|
|
55635
|
+
if (!transport && !child)
|
|
55408
55636
|
return Promise.reject(new Error(`[mcp:${this.serverName}] not started`));
|
|
55409
55637
|
const id3 = this.nextId++;
|
|
55410
|
-
const payload = JSON.stringify({ jsonrpc: "2.0", id: id3, method, params });
|
|
55411
55638
|
return new Promise((resolve9, reject) => {
|
|
55412
55639
|
const timer = setTimeout(() => {
|
|
55413
55640
|
this.pending.delete(id3);
|
|
@@ -55418,16 +55645,25 @@ var init_mcpClient = __esm({
|
|
|
55418
55645
|
);
|
|
55419
55646
|
}, timeoutMs2);
|
|
55420
55647
|
this.pending.set(id3, { resolve: resolve9, reject, timer });
|
|
55421
|
-
|
|
55422
|
-
|
|
55423
|
-
|
|
55424
|
-
|
|
55425
|
-
|
|
55426
|
-
|
|
55427
|
-
|
|
55648
|
+
if (transport) {
|
|
55649
|
+
void transport.send({ id: id3, method, params }, timeoutMs2);
|
|
55650
|
+
} else {
|
|
55651
|
+
const payload = JSON.stringify({ jsonrpc: "2.0", id: id3, method, params });
|
|
55652
|
+
child.stdin.write(payload + "\n", (err) => {
|
|
55653
|
+
if (err) {
|
|
55654
|
+
clearTimeout(timer);
|
|
55655
|
+
this.pending.delete(id3);
|
|
55656
|
+
reject(err);
|
|
55657
|
+
}
|
|
55658
|
+
});
|
|
55659
|
+
}
|
|
55428
55660
|
});
|
|
55429
55661
|
}
|
|
55430
55662
|
notify(method, params) {
|
|
55663
|
+
if (this.transport) {
|
|
55664
|
+
void this.transport.send({ method, params }, this.defaultTimeoutMs);
|
|
55665
|
+
return;
|
|
55666
|
+
}
|
|
55431
55667
|
this.child?.stdin.write(
|
|
55432
55668
|
JSON.stringify({ jsonrpc: "2.0", method, params }) + "\n"
|
|
55433
55669
|
);
|
|
@@ -55439,26 +55675,29 @@ var init_mcpClient = __esm({
|
|
|
55439
55675
|
const line = this.stdoutBuffer.slice(0, nl).trim();
|
|
55440
55676
|
this.stdoutBuffer = this.stdoutBuffer.slice(nl + 1);
|
|
55441
55677
|
if (!line) continue;
|
|
55442
|
-
let msg;
|
|
55443
55678
|
try {
|
|
55444
|
-
|
|
55679
|
+
this.handleMessage(JSON.parse(line));
|
|
55445
55680
|
} catch {
|
|
55446
55681
|
continue;
|
|
55447
55682
|
}
|
|
55448
|
-
|
|
55449
|
-
|
|
55450
|
-
|
|
55451
|
-
|
|
55452
|
-
|
|
55453
|
-
|
|
55454
|
-
|
|
55455
|
-
|
|
55456
|
-
|
|
55457
|
-
|
|
55458
|
-
|
|
55459
|
-
|
|
55460
|
-
|
|
55461
|
-
|
|
55683
|
+
}
|
|
55684
|
+
}
|
|
55685
|
+
/** Route one parsed JSON-RPC message to its pending request (both transports). */
|
|
55686
|
+
handleMessage(msg) {
|
|
55687
|
+
const m = msg;
|
|
55688
|
+
if (typeof m.id !== "number") return;
|
|
55689
|
+
const pending = this.pending.get(m.id);
|
|
55690
|
+
if (!pending) return;
|
|
55691
|
+
this.pending.delete(m.id);
|
|
55692
|
+
clearTimeout(pending.timer);
|
|
55693
|
+
if (m.error) {
|
|
55694
|
+
pending.reject(
|
|
55695
|
+
new Error(
|
|
55696
|
+
`[mcp:${this.serverName}] ${m.error.message ?? "JSON-RPC error"} (code ${m.error.code ?? "?"})`
|
|
55697
|
+
)
|
|
55698
|
+
);
|
|
55699
|
+
} else {
|
|
55700
|
+
pending.resolve(m.result);
|
|
55462
55701
|
}
|
|
55463
55702
|
}
|
|
55464
55703
|
failAll(err) {
|
|
@@ -55493,11 +55732,17 @@ function readFile6(path91) {
|
|
|
55493
55732
|
const parsed = JSON.parse(readFileSync29(path91, "utf8"));
|
|
55494
55733
|
const out = {};
|
|
55495
55734
|
for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
|
|
55496
|
-
|
|
55735
|
+
const hasCommand = !!cfg && typeof cfg.command === "string" && !!cfg.command.trim();
|
|
55736
|
+
const hasUrl = !!cfg && typeof cfg.url === "string" && /^https?:\/\//i.test(cfg.url);
|
|
55737
|
+
if (!cfg || !hasCommand && !hasUrl) continue;
|
|
55497
55738
|
out[name] = {
|
|
55498
|
-
command: cfg.command.trim(),
|
|
55739
|
+
command: hasCommand ? cfg.command.trim() : void 0,
|
|
55499
55740
|
args: Array.isArray(cfg.args) ? cfg.args.map(String) : void 0,
|
|
55500
55741
|
env: cfg.env && typeof cfg.env === "object" ? cfg.env : void 0,
|
|
55742
|
+
type: hasUrl && !hasCommand ? "http" : cfg.type === "http" ? "http" : "stdio",
|
|
55743
|
+
url: hasUrl ? cfg.url.trim() : void 0,
|
|
55744
|
+
timeoutMs: typeof cfg.timeoutMs === "number" && cfg.timeoutMs > 0 ? cfg.timeoutMs : void 0,
|
|
55745
|
+
serial: typeof cfg.serial === "boolean" ? cfg.serial : void 0,
|
|
55501
55746
|
enabled: cfg.enabled !== false
|
|
55502
55747
|
};
|
|
55503
55748
|
}
|
|
@@ -55539,8 +55784,13 @@ function upsertMcpServer(opts) {
|
|
|
55539
55784
|
error: "Invalid server name (use letters, digits, _ -)"
|
|
55540
55785
|
};
|
|
55541
55786
|
}
|
|
55542
|
-
|
|
55543
|
-
|
|
55787
|
+
const hasCommand = !!opts.config.command?.trim();
|
|
55788
|
+
const hasUrl = typeof opts.config.url === "string" && /^https?:\/\//i.test(opts.config.url);
|
|
55789
|
+
if (!hasCommand && !hasUrl) {
|
|
55790
|
+
return {
|
|
55791
|
+
ok: false,
|
|
55792
|
+
error: "either command (stdio) or url (http) is required"
|
|
55793
|
+
};
|
|
55544
55794
|
}
|
|
55545
55795
|
let path91;
|
|
55546
55796
|
if (opts.scope === "user") {
|
|
@@ -55557,9 +55807,13 @@ function upsertMcpServer(opts) {
|
|
|
55557
55807
|
}
|
|
55558
55808
|
const current = readFile6(path91);
|
|
55559
55809
|
current[name] = {
|
|
55560
|
-
command: opts.config.command.trim(),
|
|
55810
|
+
command: hasCommand ? opts.config.command.trim() : void 0,
|
|
55561
55811
|
args: opts.config.args,
|
|
55562
55812
|
env: opts.config.env,
|
|
55813
|
+
type: hasUrl ? "http" : opts.config.type === "http" ? "http" : "stdio",
|
|
55814
|
+
url: hasUrl ? opts.config.url.trim() : void 0,
|
|
55815
|
+
timeoutMs: opts.config.timeoutMs,
|
|
55816
|
+
serial: opts.config.serial,
|
|
55563
55817
|
enabled: opts.config.enabled !== false
|
|
55564
55818
|
};
|
|
55565
55819
|
writeFile2(path91, current);
|
|
@@ -55634,9 +55888,35 @@ function buildQwenMmPreset() {
|
|
|
55634
55888
|
]
|
|
55635
55889
|
};
|
|
55636
55890
|
}
|
|
55891
|
+
function buildUnrealPreset() {
|
|
55892
|
+
const url2 = process.env.UNREAL_MCP_URL?.trim() || "http://127.0.0.1:8000/mcp";
|
|
55893
|
+
return {
|
|
55894
|
+
id: "unreal-mcp",
|
|
55895
|
+
servers: {
|
|
55896
|
+
"unreal-mcp": {
|
|
55897
|
+
type: "http",
|
|
55898
|
+
url: url2,
|
|
55899
|
+
// Editor tool calls (asset scans, builds, PIE) can be slow.
|
|
55900
|
+
timeoutMs: 12e4,
|
|
55901
|
+
// Epic guidance: never overlap calls on the editor's game thread.
|
|
55902
|
+
serial: true,
|
|
55903
|
+
enabled: true
|
|
55904
|
+
}
|
|
55905
|
+
},
|
|
55906
|
+
notes: [
|
|
55907
|
+
"Unreal Engine 5.8+ \u2014 MCP server embedded in the editor (Experimental feature).",
|
|
55908
|
+
"Editor: enable the 'Model Context Protocol' plugin, then Edit \u2192 Project Settings \u2192 Plugins \u2192 MCP Server.",
|
|
55909
|
+
`Endpoint: ${url2} (override with UNREAL_MCP_URL; port/path configurable in the editor).`,
|
|
55910
|
+
"Tool Search ON by default: tools surface as list_toolsets / describe_toolset / call_tool.",
|
|
55911
|
+
"Requests are serialized and time out after 120s (editor tools can be slow).",
|
|
55912
|
+
"Start the editor before OR after zelari \u2014 unreachable servers are retried on each turn.",
|
|
55913
|
+
"Kill switch: disable the unreal-mcp server in mcp.json or ZELARI_MCP=0"
|
|
55914
|
+
]
|
|
55915
|
+
};
|
|
55916
|
+
}
|
|
55637
55917
|
function listMcpPresetIds() {
|
|
55638
55918
|
return Object.keys(PRESETS).filter(
|
|
55639
|
-
(k) => k === "cua" || k === "composio" || k === "qwen-mm-plugins"
|
|
55919
|
+
(k) => k === "cua" || k === "composio" || k === "qwen-mm-plugins" || k === "unreal-mcp"
|
|
55640
55920
|
);
|
|
55641
55921
|
}
|
|
55642
55922
|
function getMcpPreset(id3) {
|
|
@@ -55705,7 +55985,10 @@ var init_mcpPresets = __esm({
|
|
|
55705
55985
|
"cua-driver": () => CUA_DRIVER_PRESET,
|
|
55706
55986
|
composio: buildComposioPreset,
|
|
55707
55987
|
"qwen-mm-plugins": buildQwenMmPreset,
|
|
55708
|
-
"qwen-mm": buildQwenMmPreset
|
|
55988
|
+
"qwen-mm": buildQwenMmPreset,
|
|
55989
|
+
unreal: buildUnrealPreset,
|
|
55990
|
+
"unreal-mcp": buildUnrealPreset,
|
|
55991
|
+
unrealEditor: buildUnrealPreset
|
|
55709
55992
|
};
|
|
55710
55993
|
}
|
|
55711
55994
|
});
|
|
@@ -55735,7 +56018,9 @@ function readMcpConfig(projectRoot = process.cwd(), opts) {
|
|
|
55735
56018
|
try {
|
|
55736
56019
|
const parsed = JSON.parse(readFileSync30(p3, "utf8"));
|
|
55737
56020
|
for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
|
|
55738
|
-
|
|
56021
|
+
const hasCommand = !!cfg && typeof cfg.command === "string" && cfg.command.length > 0;
|
|
56022
|
+
const hasUrl = !!cfg && typeof cfg.url === "string" && /^https?:\/\//i.test(cfg.url);
|
|
56023
|
+
if (!cfg || !hasCommand && !hasUrl) continue;
|
|
55739
56024
|
merged[name] = cfg;
|
|
55740
56025
|
}
|
|
55741
56026
|
} catch {
|
|
@@ -55776,16 +56061,52 @@ async function ensureLoaded(projectRoot) {
|
|
|
55776
56061
|
}
|
|
55777
56062
|
} catch (err) {
|
|
55778
56063
|
client.close();
|
|
55779
|
-
|
|
56064
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
56065
|
+
if (isHttpConfig(cfg)) {
|
|
56066
|
+
state2.pendingHttp.push({ name, cfg });
|
|
56067
|
+
state2.warnings.push(
|
|
56068
|
+
`[mcp:${name}] not reachable yet (${msg}) \u2014 will retry each turn`
|
|
56069
|
+
);
|
|
56070
|
+
} else {
|
|
56071
|
+
state2.warnings.push(`[mcp:${name}] disabled: ${msg}`);
|
|
56072
|
+
}
|
|
55780
56073
|
}
|
|
55781
56074
|
}
|
|
55782
56075
|
}
|
|
56076
|
+
function isHttpConfig(cfg) {
|
|
56077
|
+
return cfg.type === "http" || !!cfg.url && !cfg.command;
|
|
56078
|
+
}
|
|
56079
|
+
async function retryPendingHttp() {
|
|
56080
|
+
if (state2.pendingHttp.length === 0) return;
|
|
56081
|
+
const remaining = [];
|
|
56082
|
+
for (const p3 of state2.pendingHttp) {
|
|
56083
|
+
const client = new McpClient(p3.name, p3.cfg);
|
|
56084
|
+
try {
|
|
56085
|
+
await client.start();
|
|
56086
|
+
const tools = await client.listTools();
|
|
56087
|
+
state2.clients.push(client);
|
|
56088
|
+
for (const info of tools) {
|
|
56089
|
+
state2.tools.push({
|
|
56090
|
+
registryName: sanitizeToolName(`mcp_${p3.name}_${info.name}`),
|
|
56091
|
+
serverName: p3.name,
|
|
56092
|
+
info,
|
|
56093
|
+
client
|
|
56094
|
+
});
|
|
56095
|
+
}
|
|
56096
|
+
} catch {
|
|
56097
|
+
client.close();
|
|
56098
|
+
remaining.push(p3);
|
|
56099
|
+
}
|
|
56100
|
+
}
|
|
56101
|
+
state2.pendingHttp = remaining;
|
|
56102
|
+
}
|
|
55783
56103
|
function sanitizeToolName(raw) {
|
|
55784
56104
|
return raw.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
55785
56105
|
}
|
|
55786
56106
|
async function registerMcpTools(registry4, projectRoot = process.cwd(), opts) {
|
|
55787
56107
|
if (process.env["ZELARI_MCP"] === "0") return { registered: [], warnings: [] };
|
|
55788
56108
|
await ensureLoaded(projectRoot);
|
|
56109
|
+
await retryPendingHttp();
|
|
55789
56110
|
const skipCuaForCouncil = opts?.councilMode === true && !isCuaAllowedForCouncil();
|
|
55790
56111
|
const registered = [];
|
|
55791
56112
|
for (const t of state2.tools) {
|
|
@@ -55823,6 +56144,7 @@ function closeMcpClients() {
|
|
|
55823
56144
|
state2.tools = [];
|
|
55824
56145
|
state2.loaded = false;
|
|
55825
56146
|
state2.warnings = [];
|
|
56147
|
+
state2.pendingHttp = [];
|
|
55826
56148
|
}
|
|
55827
56149
|
function _resetMcpForTests() {
|
|
55828
56150
|
closeMcpClients();
|
|
@@ -55836,7 +56158,7 @@ var init_mcpManager = __esm({
|
|
|
55836
56158
|
init_mcpClient();
|
|
55837
56159
|
init_mcpPresets();
|
|
55838
56160
|
init_folderTrust();
|
|
55839
|
-
state2 = { loaded: false, tools: [], warnings: [], clients: [] };
|
|
56161
|
+
state2 = { loaded: false, tools: [], warnings: [], clients: [], pendingHttp: [] };
|
|
55840
56162
|
}
|
|
55841
56163
|
});
|
|
55842
56164
|
|
|
@@ -67537,6 +67859,7 @@ var init_skillConfigIo = __esm({
|
|
|
67537
67859
|
"@zelari/core/skills/builtin/planning",
|
|
67538
67860
|
"@zelari/core/skills/builtin/refactoring",
|
|
67539
67861
|
"@zelari/core/skills/builtin/review",
|
|
67862
|
+
"@zelari/core/skills/builtin/unrealEditor",
|
|
67540
67863
|
"@zelari/core/skills/builtin/testing",
|
|
67541
67864
|
"@zelari/core/skills/builtin/schema-loop",
|
|
67542
67865
|
"@zelari/core/skills/builtin/computer-use-cua",
|
|
@@ -80423,7 +80746,7 @@ function pickRootComponent() {
|
|
|
80423
80746
|
}
|
|
80424
80747
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
80425
80748
|
console.log(
|
|
80426
|
-
"zelari-code \u2014 AI Council coding agent CLI.\n\nUsage: zelari-code [options]\n\nOptions:\n --version, -v Print version and exit\n --help, -h Print this help and exit\n --doctor Diagnose install health (shim, bundle, PATH, deps,\n node/git/bash in the agent shell)\n --inspect [--json] Unified project inspection (config, skills, MCP,\n hooks, plugins, AGENTS.md, trust status)\n --trust [path] Trust the cwd (or path) so project MCP + hooks load\n --fix-path Add the npm global prefix to the user PATH\n (Windows only; fixes 'command not found' after install)\n --fix-budget Set recommended ZELARI_MAX_TOOL_LOOP_HARD=180,\n ZELARI_MAX_TOOL_LOOP_ITERATIONS=60, ZELARI_CONTEXT_LIMIT=400000\n at User scope (prevents the agent stopping mid-task)\n --permission-mcp <socket> MCP stdio server for external agent permission prompts\n (spawned by claude --permission-prompt-tool)\n --memory-mcp Optional MCP stdio server for project memory\n --cwd <path> Trusted project root (requires ZELARI_MEMORY_MCP=1)\n --client-id <id> Stable local owner identity for private memories\n --memory-json <json> Read-only project-memory bridge for Zelari Desktop\n --skip-checks Skip the boot-time prerequisite check\n (alias for ZELARI_SKIP_PREFLIGHT=1)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --mode kraken|council|zelari Dispatch mode (default: kraken; agent=alias)\n --council Alias for --mode council\n --phase plan|build Work phase (default: build)\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n --profile <id> Capability profile (minimal/v1|kraken/v1|council/v1|mission/v1)\n --resume <id> Continue a 2.0 spine session\n --export-session <path> Write zelari-session-export/1 after the run\n --strict-done Evidence-based BUILD completion gate\n --session-export <id> Print a portable 2.0 session export (no LLM)\n serve Companion host for Android/remote clients (Tailscale)\n --bind <ip> Listen address (default: 127.0.0.1; use Tailscale IP)\n --port <n> Port (default: 7421)\n --token <secret> Bearer token (default: ~/.zelari-code/companion.token)\n --project <path> Allowlisted project root (repeatable)\n --save-projects Persist --project list to companion.json\n --print-config Print provider/model config as JSON (no secrets)\n --plugins-status JSON status of optional plugins (Playwright, eslint, \u2026)\n --plugins-install <id> Install plugin (playwright also fetches Chromium)\n --cwd <path> Workspace for -D installs (default: process.cwd())\n --set-config Persist provider/model/endpoint\n --provider <id> Set active provider\n --model <id> Set model for that provider\n --endpoint <url> Custom OpenAI-compatible base URL\n --endpoint-clear Remove custom endpoint override\n --thinking <spec> Thinking effort (auto|off|low|medium|high|budget:N)\n --set-key Store an API key (never printed back)\n --provider <id> Provider id (required)\n --key <secret> API key (required)\n --login-oauth Start subscription OAuth (grok, chatgpt, anthropic)\n --provider <id> grok | chatgpt | anthropic\n --code <paste> Anthropic magic-link code (CODE#STATE)\n --no-browser Do not open the system browser\n --refresh-oauth Force-refresh an OAuth access token\n --provider <id> grok | chatgpt | anthropic\n --logout-oauth Clear stored OAuth credentials\n --provider <id> grok | chatgpt | anthropic\n --discover-models Refresh model list for a provider\n --provider <id> Provider (default: active)\n --print-mcp Print MCP server config (user + project)\n --cwd <path> Project root for .zelari/mcp.json\n --set-mcp Add/update an MCP server entry\n --name <id> Server name (required)\n --command <bin> Executable (
|
|
80749
|
+
"zelari-code \u2014 AI Council coding agent CLI.\n\nUsage: zelari-code [options]\n\nOptions:\n --version, -v Print version and exit\n --help, -h Print this help and exit\n --doctor Diagnose install health (shim, bundle, PATH, deps,\n node/git/bash in the agent shell)\n --inspect [--json] Unified project inspection (config, skills, MCP,\n hooks, plugins, AGENTS.md, trust status)\n --trust [path] Trust the cwd (or path) so project MCP + hooks load\n --fix-path Add the npm global prefix to the user PATH\n (Windows only; fixes 'command not found' after install)\n --fix-budget Set recommended ZELARI_MAX_TOOL_LOOP_HARD=180,\n ZELARI_MAX_TOOL_LOOP_ITERATIONS=60, ZELARI_CONTEXT_LIMIT=400000\n at User scope (prevents the agent stopping mid-task)\n --permission-mcp <socket> MCP stdio server for external agent permission prompts\n (spawned by claude --permission-prompt-tool)\n --memory-mcp Optional MCP stdio server for project memory\n --cwd <path> Trusted project root (requires ZELARI_MEMORY_MCP=1)\n --client-id <id> Stable local owner identity for private memories\n --memory-json <json> Read-only project-memory bridge for Zelari Desktop\n --skip-checks Skip the boot-time prerequisite check\n (alias for ZELARI_SKIP_PREFLIGHT=1)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --mode kraken|council|zelari Dispatch mode (default: kraken; agent=alias)\n --council Alias for --mode council\n --phase plan|build Work phase (default: build)\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n --profile <id> Capability profile (minimal/v1|kraken/v1|council/v1|mission/v1)\n --resume <id> Continue a 2.0 spine session\n --export-session <path> Write zelari-session-export/1 after the run\n --strict-done Evidence-based BUILD completion gate\n --session-export <id> Print a portable 2.0 session export (no LLM)\n serve Companion host for Android/remote clients (Tailscale)\n --bind <ip> Listen address (default: 127.0.0.1; use Tailscale IP)\n --port <n> Port (default: 7421)\n --token <secret> Bearer token (default: ~/.zelari-code/companion.token)\n --project <path> Allowlisted project root (repeatable)\n --save-projects Persist --project list to companion.json\n --print-config Print provider/model config as JSON (no secrets)\n --plugins-status JSON status of optional plugins (Playwright, eslint, \u2026)\n --plugins-install <id> Install plugin (playwright also fetches Chromium)\n --cwd <path> Workspace for -D installs (default: process.cwd())\n --set-config Persist provider/model/endpoint\n --provider <id> Set active provider\n --model <id> Set model for that provider\n --endpoint <url> Custom OpenAI-compatible base URL\n --endpoint-clear Remove custom endpoint override\n --thinking <spec> Thinking effort (auto|off|low|medium|high|budget:N)\n --set-key Store an API key (never printed back)\n --provider <id> Provider id (required)\n --key <secret> API key (required)\n --login-oauth Start subscription OAuth (grok, chatgpt, anthropic)\n --provider <id> grok | chatgpt | anthropic\n --code <paste> Anthropic magic-link code (CODE#STATE)\n --no-browser Do not open the system browser\n --refresh-oauth Force-refresh an OAuth access token\n --provider <id> grok | chatgpt | anthropic\n --logout-oauth Clear stored OAuth credentials\n --provider <id> grok | chatgpt | anthropic\n --discover-models Refresh model list for a provider\n --provider <id> Provider (default: active)\n --print-mcp Print MCP server config (user + project)\n --cwd <path> Project root for .zelari/mcp.json\n --set-mcp Add/update an MCP server entry\n --name <id> Server name (required)\n --command <bin> Executable for stdio servers\n --url <endpoint> Streamable HTTP endpoint (e.g. UE 5.8 editor)\n --args <json> JSON array of args (stdio, optional)\n --timeout <ms> Per-server request timeout (http, optional)\n --scope user|project Default: user\n --enabled true|false Default: true\n --cwd <path> Required when scope=project\n --set-mcp-preset Install a named MCP preset (e.g. cua)\n --preset cua Cua Driver desktop computer-use (MCP)\n --preset unreal-mcp Unreal Engine 5.8+ editor (MCP over HTTP)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-mcp Remove an MCP server entry\n --name <id> --scope user|project [--cwd <path>]\n --print-skills Print skills (builtin + user + project)\n --cwd <path> Project root for .zelari/skills\n --set-skill Add/update a SKILL.md skill\n --name <id> Skill id (required)\n --description <t> One-line description (required)\n --body <text> Markdown instructions (required)\n --category <c> plan|refactor|debug|review|test|docs|ops|git|db|maint\n --tools <csv> Comma-separated required tools (optional)\n --cost <l> low|medium|high (default: medium)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-skill Remove a user/project SKILL.md\n --name <id> --scope user|project [--cwd <path>]\n --generate-skill-from-url Fetch URL + draft skill via model (JSON)\n --url <https://...> Required\n --provider <id> Override active provider\n --model <name> Override model for the selected provider\n --print-ssh-targets Print SSH deploy/monitor targets\n --set-ssh-target Upsert target (--json '{...}' or flags)\n --remove-ssh-target --id <id>\n --test-ssh-target --id <id> (BatchMode ssh true)\n --print-ssh-pubkey --path <private-or-.pub> (display public key)\n\nEnvironment:\n ZELARI_NO_WIZARD=1 Skip the first-run wizard\n ZELARI_SKIP_PREFLIGHT=1 Skip the boot prerequisite check\n ZELARI_NO_PLUGIN_PROMPT=1 Skip the boot plugin-install prompt\n ZELARI_MEMORY_MCP=1 Enable the optional external memory MCP server\n ANATHEMA_DEV=1 Disable background update check + preflight\n"
|
|
80427
80750
|
);
|
|
80428
80751
|
process.exit(0);
|
|
80429
80752
|
}
|
|
@@ -80449,6 +80772,8 @@ function pickRootComponent() {
|
|
|
80449
80772
|
};
|
|
80450
80773
|
const name = get("--name");
|
|
80451
80774
|
const command = get("--command");
|
|
80775
|
+
const url2 = get("--url");
|
|
80776
|
+
const timeoutRaw = get("--timeout");
|
|
80452
80777
|
const scopeRaw = get("--scope") ?? "user";
|
|
80453
80778
|
const scope = scopeRaw === "project" ? "project" : "user";
|
|
80454
80779
|
const cwd = get("--cwd") ?? process.cwd();
|
|
@@ -80461,14 +80786,22 @@ function pickRootComponent() {
|
|
|
80461
80786
|
if (!Array.isArray(parsed)) throw new Error("--args must be a JSON array");
|
|
80462
80787
|
args = parsed.map(String);
|
|
80463
80788
|
}
|
|
80464
|
-
|
|
80465
|
-
|
|
80789
|
+
const timeoutMs2 = timeoutRaw !== void 0 ? Number(timeoutRaw) : void 0;
|
|
80790
|
+
if (timeoutMs2 !== void 0 && (!Number.isFinite(timeoutMs2) || timeoutMs2 <= 0)) {
|
|
80791
|
+
throw new Error("--timeout must be a positive number of milliseconds");
|
|
80792
|
+
}
|
|
80793
|
+
if (!name) throw new Error("--name is required");
|
|
80794
|
+
if (!command && !url2) {
|
|
80795
|
+
throw new Error("either --command (stdio) or --url (http) is required");
|
|
80796
|
+
}
|
|
80797
|
+
if (url2 && !/^https?:\/\//i.test(url2)) {
|
|
80798
|
+
throw new Error("--url must be an http(s) endpoint");
|
|
80466
80799
|
}
|
|
80467
80800
|
const result = upsertMcpServer({
|
|
80468
80801
|
scope,
|
|
80469
80802
|
name,
|
|
80470
80803
|
projectRoot: cwd,
|
|
80471
|
-
config: { command, args, enabled }
|
|
80804
|
+
config: url2 ? { type: "http", url: url2, timeoutMs: timeoutMs2, serial: true, enabled } : { command, args, enabled }
|
|
80472
80805
|
});
|
|
80473
80806
|
if (!result.ok) throw new Error(result.error);
|
|
80474
80807
|
console.log(JSON.stringify({ ok: true, path: result.path, name, scope }));
|