@kwonye/mcpx 0.1.85 → 0.1.88
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/adapters/claude.d.ts.map +1 -1
- package/dist/{cli-bq7zsrv6.js → cli-btx8jwra.js} +20 -63
- package/dist/{cli-dvj5waat.js → cli-qfvawh8e.js} +32 -5
- package/dist/{cli-8tdwfb4w.js → cli-vwnh1mqg.js} +193 -263
- package/dist/{cli-p2mah4z3.js → cli-w4p1t10f.js} +194 -264
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +12 -17
- package/dist/{config-ajxqtkq6.js → config-nkgc4c4n.js} +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +5 -3
- package/dist/core/registry.d.ts +1 -0
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/status.d.ts.map +1 -1
- package/dist/core/sync.d.ts.map +1 -1
- package/dist/gateway/server.d.ts +7 -5
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/{registry-j53hz9d4.js → registry-vykv33et.js} +3 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
loadConfig,
|
|
3
3
|
loadMergedConfig,
|
|
4
4
|
saveConfig
|
|
5
|
-
} from "./cli-
|
|
5
|
+
} from "./cli-btx8jwra.js";
|
|
6
6
|
import {
|
|
7
7
|
SecretsManager,
|
|
8
8
|
ensureGatewayToken
|
|
9
|
-
} from "./cli-
|
|
9
|
+
} from "./cli-qfvawh8e.js";
|
|
10
10
|
import {
|
|
11
11
|
ensureDir,
|
|
12
12
|
ensureParentDir,
|
|
@@ -290,6 +290,7 @@ function mapRegistryToSpec(_name, option, resolvedValues) {
|
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
// src/adapters/claude.ts
|
|
293
|
+
import fs2 from "node:fs";
|
|
293
294
|
import path2 from "node:path";
|
|
294
295
|
import { z } from "zod";
|
|
295
296
|
|
|
@@ -352,6 +353,9 @@ function errorResult(clientId, configPath, message) {
|
|
|
352
353
|
message
|
|
353
354
|
};
|
|
354
355
|
}
|
|
356
|
+
function managedGatewayEntryName(serverName) {
|
|
357
|
+
return `${serverName} (mcpx)`;
|
|
358
|
+
}
|
|
355
359
|
function isManagedGatewayProjection(entryName) {
|
|
356
360
|
return entryName.endsWith(" (mcpx)");
|
|
357
361
|
}
|
|
@@ -419,6 +423,22 @@ function syncMcpExcludedArray(raw, managedNames, managedEntries) {
|
|
|
419
423
|
}
|
|
420
424
|
|
|
421
425
|
// src/adapters/claude.ts
|
|
426
|
+
function normalizeProjectKey(p) {
|
|
427
|
+
try {
|
|
428
|
+
return fs2.realpathSync(path2.resolve(p));
|
|
429
|
+
} catch {
|
|
430
|
+
return path2.resolve(p);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function syncProjectDisabledArray(project, managedNames, disabledForThisProject) {
|
|
434
|
+
const existingNonManaged = (project.disabledMcpServers ?? []).filter((name) => !managedNames.includes(name));
|
|
435
|
+
const combined = [...existingNonManaged, ...disabledForThisProject];
|
|
436
|
+
if (combined.length > 0) {
|
|
437
|
+
project.disabledMcpServers = combined;
|
|
438
|
+
} else {
|
|
439
|
+
delete project.disabledMcpServers;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
422
442
|
var stringMapSchema = z.record(z.string(), z.string());
|
|
423
443
|
var claudeEntrySchema = z.object({
|
|
424
444
|
type: z.string().optional(),
|
|
@@ -531,12 +551,26 @@ class ClaudeAdapter {
|
|
|
531
551
|
}
|
|
532
552
|
raw.mcpServers = topLevelServers;
|
|
533
553
|
syncDisabledMcpServersArray(raw, managedNames, options.managedEntries);
|
|
554
|
+
const scopeByNormalizedPath = new Map;
|
|
555
|
+
for (const scope of options.projectScopes ?? []) {
|
|
556
|
+
scopeByNormalizedPath.set(normalizeProjectKey(scope.path), scope.disabledServerNames);
|
|
557
|
+
}
|
|
534
558
|
const projects = raw.projects ?? {};
|
|
535
559
|
for (const projectPath of Object.keys(projects)) {
|
|
536
560
|
const project = projects[projectPath] ?? {};
|
|
537
|
-
|
|
561
|
+
const disabledForThis = scopeByNormalizedPath.get(normalizeProjectKey(projectPath)) ?? [];
|
|
562
|
+
syncProjectDisabledArray(project, managedNames, disabledForThis);
|
|
538
563
|
projects[projectPath] = project;
|
|
539
564
|
}
|
|
565
|
+
for (const scope of options.projectScopes ?? []) {
|
|
566
|
+
if (scope.disabledServerNames.length === 0)
|
|
567
|
+
continue;
|
|
568
|
+
const normalizedKey = normalizeProjectKey(scope.path);
|
|
569
|
+
const alreadyExists = Object.keys(projects).some((p) => normalizeProjectKey(p) === normalizedKey);
|
|
570
|
+
if (!alreadyExists) {
|
|
571
|
+
projects[scope.path] = { disabledMcpServers: [...scope.disabledServerNames] };
|
|
572
|
+
}
|
|
573
|
+
}
|
|
540
574
|
raw.projects = projects;
|
|
541
575
|
writeJsonAtomic(configPath, raw);
|
|
542
576
|
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(Object.entries(serverEntries).map(([name, entry]) => [name, JSON.stringify(entry)])));
|
|
@@ -680,7 +714,7 @@ class ClaudeDesktopAdapter {
|
|
|
680
714
|
|
|
681
715
|
// src/adapters/codex.ts
|
|
682
716
|
import path4 from "node:path";
|
|
683
|
-
import
|
|
717
|
+
import fs3 from "node:fs";
|
|
684
718
|
import { parse, stringify } from "@iarna/toml";
|
|
685
719
|
import { z as z3 } from "zod";
|
|
686
720
|
var stringMapSchema3 = z3.preprocess((value) => {
|
|
@@ -711,10 +745,10 @@ class CodexAdapter {
|
|
|
711
745
|
scanForImports(_config, managedIndex) {
|
|
712
746
|
const configPath = this.detectConfigPath();
|
|
713
747
|
const result = emptyImportScan(this.id, configPath ?? undefined);
|
|
714
|
-
if (!configPath || !
|
|
748
|
+
if (!configPath || !fs3.existsSync(configPath)) {
|
|
715
749
|
return result;
|
|
716
750
|
}
|
|
717
|
-
const existing =
|
|
751
|
+
const existing = fs3.readFileSync(configPath, "utf8");
|
|
718
752
|
const doc = existing.trim().length > 0 ? parse(existing) : {};
|
|
719
753
|
const mcpServers = doc.mcp_servers ?? {};
|
|
720
754
|
for (const [name, rawEntry] of Object.entries(mcpServers)) {
|
|
@@ -769,7 +803,7 @@ class CodexAdapter {
|
|
|
769
803
|
return errorResult(this.id, undefined, "Unable to resolve Codex config.toml path.");
|
|
770
804
|
}
|
|
771
805
|
try {
|
|
772
|
-
const existing =
|
|
806
|
+
const existing = fs3.existsSync(configPath) ? fs3.readFileSync(configPath, "utf8") : "";
|
|
773
807
|
const doc = existing.trim().length > 0 ? parse(existing) : {};
|
|
774
808
|
const mcpServers = doc.mcp_servers ?? {};
|
|
775
809
|
removeSourceEntries(mcpServers, options.sourceEntriesToRemove);
|
|
@@ -790,10 +824,10 @@ class CodexAdapter {
|
|
|
790
824
|
mcpServers[name] = entry;
|
|
791
825
|
}
|
|
792
826
|
doc.mcp_servers = mcpServers;
|
|
793
|
-
|
|
827
|
+
fs3.mkdirSync(path4.dirname(configPath), { recursive: true });
|
|
794
828
|
const tmpPath = `${configPath}.tmp-${process.pid}-${Date.now()}`;
|
|
795
|
-
|
|
796
|
-
|
|
829
|
+
fs3.writeFileSync(tmpPath, stringify(doc), { mode: 384 });
|
|
830
|
+
fs3.renameSync(tmpPath, configPath);
|
|
797
831
|
setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(Object.entries(serverEntries).map(([name, entry]) => [name, JSON.stringify(entry)])));
|
|
798
832
|
return okResult(this.id, configPath);
|
|
799
833
|
} catch (error) {
|
|
@@ -933,7 +967,7 @@ class CursorAdapter {
|
|
|
933
967
|
|
|
934
968
|
// src/adapters/cline.ts
|
|
935
969
|
import path6 from "node:path";
|
|
936
|
-
import
|
|
970
|
+
import fs4 from "node:fs";
|
|
937
971
|
import { z as z5 } from "zod";
|
|
938
972
|
var stringMapSchema5 = z5.record(z5.string(), z5.string());
|
|
939
973
|
var clineEntrySchema = z5.object({
|
|
@@ -970,7 +1004,7 @@ class ClineAdapter {
|
|
|
970
1004
|
detectConfigPath() {
|
|
971
1005
|
const candidates = getCandidatePaths();
|
|
972
1006
|
for (const candidate of candidates) {
|
|
973
|
-
if (
|
|
1007
|
+
if (fs4.existsSync(candidate)) {
|
|
974
1008
|
return candidate;
|
|
975
1009
|
}
|
|
976
1010
|
}
|
|
@@ -982,7 +1016,7 @@ class ClineAdapter {
|
|
|
982
1016
|
scanForImports(_config, managedIndex) {
|
|
983
1017
|
const configPath = this.detectConfigPath();
|
|
984
1018
|
const result = emptyImportScan(this.id, configPath ?? undefined);
|
|
985
|
-
if (!configPath || !
|
|
1019
|
+
if (!configPath || !fs4.existsSync(configPath)) {
|
|
986
1020
|
return result;
|
|
987
1021
|
}
|
|
988
1022
|
const raw = readJsonFile(configPath, {});
|
|
@@ -1040,7 +1074,7 @@ class ClineAdapter {
|
|
|
1040
1074
|
if (!configPath) {
|
|
1041
1075
|
return errorResult(this.id, undefined, "Unable to resolve Cline MCP config path.");
|
|
1042
1076
|
}
|
|
1043
|
-
if (!
|
|
1077
|
+
if (!fs4.existsSync(configPath)) {
|
|
1044
1078
|
return okResult(this.id, configPath, "Cline config not found; skipping sync.");
|
|
1045
1079
|
}
|
|
1046
1080
|
try {
|
|
@@ -1080,7 +1114,7 @@ class ClineAdapter {
|
|
|
1080
1114
|
}
|
|
1081
1115
|
|
|
1082
1116
|
// src/adapters/kiro.ts
|
|
1083
|
-
import
|
|
1117
|
+
import fs5 from "node:fs";
|
|
1084
1118
|
import path7 from "node:path";
|
|
1085
1119
|
import { z as z6 } from "zod";
|
|
1086
1120
|
var stringMapSchema6 = z6.record(z6.string(), z6.string());
|
|
@@ -1198,22 +1232,22 @@ class KiroAdapter {
|
|
|
1198
1232
|
}
|
|
1199
1233
|
syncSkills(skills) {
|
|
1200
1234
|
const skillsDir = path7.join(homeDir(), ".kiro", "skills");
|
|
1201
|
-
if (!
|
|
1202
|
-
|
|
1235
|
+
if (!fs5.existsSync(skillsDir)) {
|
|
1236
|
+
fs5.mkdirSync(skillsDir, { recursive: true });
|
|
1203
1237
|
}
|
|
1204
|
-
const existingFiles =
|
|
1238
|
+
const existingFiles = fs5.readdirSync(skillsDir);
|
|
1205
1239
|
const managedIds = new Set(skills.map((s) => s.id));
|
|
1206
1240
|
for (const file of existingFiles) {
|
|
1207
1241
|
if (file.endsWith(".md")) {
|
|
1208
1242
|
const id = path7.basename(file, ".md");
|
|
1209
1243
|
if (!managedIds.has(id)) {
|
|
1210
|
-
|
|
1244
|
+
fs5.unlinkSync(path7.join(skillsDir, file));
|
|
1211
1245
|
}
|
|
1212
1246
|
}
|
|
1213
1247
|
}
|
|
1214
1248
|
for (const skill of skills) {
|
|
1215
1249
|
const targetPath = path7.join(skillsDir, `${skill.id}.md`);
|
|
1216
|
-
|
|
1250
|
+
fs5.writeFileSync(targetPath, skill.content, "utf-8");
|
|
1217
1251
|
}
|
|
1218
1252
|
}
|
|
1219
1253
|
}
|
|
@@ -1468,7 +1502,7 @@ class VsCodeAdapter {
|
|
|
1468
1502
|
}
|
|
1469
1503
|
|
|
1470
1504
|
// src/adapters/qwen.ts
|
|
1471
|
-
import
|
|
1505
|
+
import fs6 from "node:fs";
|
|
1472
1506
|
import path10 from "node:path";
|
|
1473
1507
|
import { z as z9 } from "zod";
|
|
1474
1508
|
var stringMapSchema9 = z9.record(z9.string(), z9.string());
|
|
@@ -1583,22 +1617,22 @@ class QwenAdapter {
|
|
|
1583
1617
|
}
|
|
1584
1618
|
syncSkills(skills) {
|
|
1585
1619
|
const skillsDir = path10.join(homeDir(), ".qwen", "skills");
|
|
1586
|
-
if (!
|
|
1587
|
-
|
|
1620
|
+
if (!fs6.existsSync(skillsDir)) {
|
|
1621
|
+
fs6.mkdirSync(skillsDir, { recursive: true });
|
|
1588
1622
|
}
|
|
1589
|
-
const existingFiles =
|
|
1623
|
+
const existingFiles = fs6.readdirSync(skillsDir);
|
|
1590
1624
|
const managedIds = new Set(skills.map((s) => s.id));
|
|
1591
1625
|
for (const file of existingFiles) {
|
|
1592
1626
|
if (file.endsWith(".md")) {
|
|
1593
1627
|
const id = path10.basename(file, ".md");
|
|
1594
1628
|
if (!managedIds.has(id)) {
|
|
1595
|
-
|
|
1629
|
+
fs6.unlinkSync(path10.join(skillsDir, file));
|
|
1596
1630
|
}
|
|
1597
1631
|
}
|
|
1598
1632
|
}
|
|
1599
1633
|
for (const skill of skills) {
|
|
1600
1634
|
const targetPath = path10.join(skillsDir, `${skill.id}.md`);
|
|
1601
|
-
|
|
1635
|
+
fs6.writeFileSync(targetPath, skill.content, "utf-8");
|
|
1602
1636
|
}
|
|
1603
1637
|
}
|
|
1604
1638
|
}
|
|
@@ -1643,6 +1677,13 @@ function saveManagedIndex(index, filePath = getManagedIndexPath()) {
|
|
|
1643
1677
|
function getGatewayUrl(config) {
|
|
1644
1678
|
return `http://127.0.0.1:${config.gateway.port}/mcp`;
|
|
1645
1679
|
}
|
|
1680
|
+
function buildProjectScopes(config) {
|
|
1681
|
+
const globallyEnabled = new Set(Object.entries(config.servers).filter(([, spec]) => isServerEnabled(spec)).map(([name]) => name));
|
|
1682
|
+
return Object.values(config.projects ?? {}).map((project) => {
|
|
1683
|
+
const disabledServerNames = (project.disabledServers ?? []).filter((name) => globallyEnabled.has(name)).map((name) => managedGatewayEntryName(name));
|
|
1684
|
+
return { path: project.path, disabledServerNames };
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1646
1687
|
function buildManagedEntries(config, gatewayUrl, localToken) {
|
|
1647
1688
|
const entries = Object.entries(config.servers);
|
|
1648
1689
|
return entries.map(([name, spec]) => ({
|
|
@@ -1752,7 +1793,8 @@ function syncAllClients(config, secrets, targetClients) {
|
|
|
1752
1793
|
managedEntries,
|
|
1753
1794
|
managedIndex,
|
|
1754
1795
|
managedIndexPath,
|
|
1755
|
-
sourceEntriesToRemove: Array.from(sourceEntriesToRemove.get(adapter.id) ?? [])
|
|
1796
|
+
sourceEntriesToRemove: Array.from(sourceEntriesToRemove.get(adapter.id) ?? []),
|
|
1797
|
+
projectScopes: buildProjectScopes(config)
|
|
1756
1798
|
});
|
|
1757
1799
|
results.push(result);
|
|
1758
1800
|
}
|
|
@@ -1887,11 +1929,11 @@ function maybePrefixBearer(target, value, raw = false) {
|
|
|
1887
1929
|
// src/core/spawn-env.ts
|
|
1888
1930
|
import os from "node:os";
|
|
1889
1931
|
import path11 from "node:path";
|
|
1890
|
-
import
|
|
1932
|
+
import fs7 from "node:fs";
|
|
1891
1933
|
function existingNvmCurrentBin() {
|
|
1892
1934
|
const nvmDir = process.env.NVM_DIR ?? path11.join(os.homedir(), ".nvm");
|
|
1893
1935
|
const current = path11.join(nvmDir, "current", "bin");
|
|
1894
|
-
return
|
|
1936
|
+
return fs7.existsSync(current) ? current : null;
|
|
1895
1937
|
}
|
|
1896
1938
|
function buildEnrichedPath(current = process.env.PATH ?? "") {
|
|
1897
1939
|
const candidates = [
|
|
@@ -2211,7 +2253,7 @@ async function getOAuthAccessToken(serverName, spec, secrets, options = {}) {
|
|
|
2211
2253
|
var APP_VERSION = "0.1.3";
|
|
2212
2254
|
|
|
2213
2255
|
// src/core/update.ts
|
|
2214
|
-
import
|
|
2256
|
+
import fs8 from "node:fs";
|
|
2215
2257
|
import path12 from "node:path";
|
|
2216
2258
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
2217
2259
|
function compareVersions(a, b) {
|
|
@@ -2251,17 +2293,17 @@ async function checkForUpdates() {
|
|
|
2251
2293
|
}
|
|
2252
2294
|
function getStagedUpdate() {
|
|
2253
2295
|
const stagedPath = getStagedVersionPath();
|
|
2254
|
-
if (!
|
|
2296
|
+
if (!fs8.existsSync(stagedPath)) {
|
|
2255
2297
|
return null;
|
|
2256
2298
|
}
|
|
2257
2299
|
try {
|
|
2258
|
-
const raw =
|
|
2300
|
+
const raw = fs8.readFileSync(stagedPath, "utf8");
|
|
2259
2301
|
const info = JSON.parse(raw);
|
|
2260
2302
|
if (!info.version || !info.cliPath) {
|
|
2261
2303
|
return null;
|
|
2262
2304
|
}
|
|
2263
2305
|
const absoluteCliPath = path12.isAbsolute(info.cliPath) ? info.cliPath : path12.join(getUpdatesDir(), info.cliPath);
|
|
2264
|
-
if (!
|
|
2306
|
+
if (!fs8.existsSync(absoluteCliPath)) {
|
|
2265
2307
|
return null;
|
|
2266
2308
|
}
|
|
2267
2309
|
return {
|
|
@@ -2280,15 +2322,15 @@ function stageUpdate(version, cliPath) {
|
|
|
2280
2322
|
cliPath: path12.isAbsolute(cliPath) ? path12.relative(updatesDir, cliPath) : cliPath,
|
|
2281
2323
|
stagedAt: new Date().toISOString()
|
|
2282
2324
|
};
|
|
2283
|
-
|
|
2325
|
+
fs8.writeFileSync(getStagedVersionPath(), JSON.stringify(stagedInfo, null, 2), "utf8");
|
|
2284
2326
|
}
|
|
2285
2327
|
function shouldUseStagedCli(stagedVersion, currentVersion) {
|
|
2286
2328
|
return compareVersions(stagedVersion, currentVersion) > 0;
|
|
2287
2329
|
}
|
|
2288
2330
|
function clearStagedUpdate() {
|
|
2289
2331
|
const stagedPath = getStagedVersionPath();
|
|
2290
|
-
if (
|
|
2291
|
-
|
|
2332
|
+
if (fs8.existsSync(stagedPath)) {
|
|
2333
|
+
fs8.unlinkSync(stagedPath);
|
|
2292
2334
|
}
|
|
2293
2335
|
}
|
|
2294
2336
|
function getStagedCliPath() {
|
|
@@ -2297,10 +2339,10 @@ function getStagedCliPath() {
|
|
|
2297
2339
|
}
|
|
2298
2340
|
function removeOldVersions(keepCount = 2) {
|
|
2299
2341
|
const updatesDir = getUpdatesDir();
|
|
2300
|
-
if (!
|
|
2342
|
+
if (!fs8.existsSync(updatesDir)) {
|
|
2301
2343
|
return;
|
|
2302
2344
|
}
|
|
2303
|
-
const entries =
|
|
2345
|
+
const entries = fs8.readdirSync(updatesDir, { withFileTypes: true });
|
|
2304
2346
|
const versionDirs = entries.filter((entry) => entry.isDirectory() && entry.name.startsWith("v")).map((entry) => entry.name).sort((a, b) => compareVersions(b.slice(1), a.slice(1)));
|
|
2305
2347
|
if (versionDirs.length <= keepCount) {
|
|
2306
2348
|
return;
|
|
@@ -2308,20 +2350,20 @@ function removeOldVersions(keepCount = 2) {
|
|
|
2308
2350
|
for (const oldVersion of versionDirs.slice(keepCount)) {
|
|
2309
2351
|
const oldPath = path12.join(updatesDir, oldVersion);
|
|
2310
2352
|
try {
|
|
2311
|
-
|
|
2353
|
+
fs8.rmSync(oldPath, { recursive: true, force: true });
|
|
2312
2354
|
} catch {}
|
|
2313
2355
|
}
|
|
2314
2356
|
}
|
|
2315
2357
|
|
|
2316
2358
|
// src/core/update-manager.ts
|
|
2317
|
-
import
|
|
2359
|
+
import fs9 from "node:fs";
|
|
2318
2360
|
import path13 from "node:path";
|
|
2319
2361
|
import { execSync, spawn } from "node:child_process";
|
|
2320
2362
|
function acquireLock() {
|
|
2321
2363
|
const lockPath = getUpdateLockPath();
|
|
2322
2364
|
try {
|
|
2323
2365
|
ensureDir(path13.dirname(lockPath));
|
|
2324
|
-
|
|
2366
|
+
fs9.writeFileSync(lockPath, `${process.pid}
|
|
2325
2367
|
`, { flag: "wx" });
|
|
2326
2368
|
return true;
|
|
2327
2369
|
} catch {
|
|
@@ -2331,25 +2373,25 @@ function acquireLock() {
|
|
|
2331
2373
|
function releaseLock() {
|
|
2332
2374
|
const lockPath = getUpdateLockPath();
|
|
2333
2375
|
try {
|
|
2334
|
-
|
|
2376
|
+
fs9.unlinkSync(lockPath);
|
|
2335
2377
|
} catch {}
|
|
2336
2378
|
}
|
|
2337
2379
|
function isUpdateInProgress() {
|
|
2338
2380
|
const lockPath = getUpdateLockPath();
|
|
2339
|
-
if (!
|
|
2381
|
+
if (!fs9.existsSync(lockPath)) {
|
|
2340
2382
|
return false;
|
|
2341
2383
|
}
|
|
2342
2384
|
try {
|
|
2343
|
-
const raw =
|
|
2385
|
+
const raw = fs9.readFileSync(lockPath, "utf8").trim();
|
|
2344
2386
|
const pid = Number(raw);
|
|
2345
2387
|
if (!Number.isFinite(pid) || pid <= 0) {
|
|
2346
|
-
|
|
2388
|
+
fs9.unlinkSync(lockPath);
|
|
2347
2389
|
return false;
|
|
2348
2390
|
}
|
|
2349
2391
|
process.kill(pid, 0);
|
|
2350
2392
|
return true;
|
|
2351
2393
|
} catch {
|
|
2352
|
-
|
|
2394
|
+
fs9.unlinkSync(lockPath);
|
|
2353
2395
|
return false;
|
|
2354
2396
|
}
|
|
2355
2397
|
}
|
|
@@ -2361,7 +2403,7 @@ async function downloadAndStageUpdate() {
|
|
|
2361
2403
|
const targetVersion = updateStatus.latestVersion;
|
|
2362
2404
|
const updatesDir = getUpdatesDir();
|
|
2363
2405
|
const versionDir = path13.join(updatesDir, `v${targetVersion}`);
|
|
2364
|
-
if (
|
|
2406
|
+
if (fs9.existsSync(versionDir)) {
|
|
2365
2407
|
const existingStaged = getStagedUpdate();
|
|
2366
2408
|
if (existingStaged?.version === targetVersion) {
|
|
2367
2409
|
return { success: false, error: `Version ${targetVersion} already staged` };
|
|
@@ -2374,7 +2416,7 @@ async function downloadAndStageUpdate() {
|
|
|
2374
2416
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2375
2417
|
});
|
|
2376
2418
|
const tarballPath = path13.join(versionDir, `kwonye-mcpx-${targetVersion}.tgz`);
|
|
2377
|
-
if (!
|
|
2419
|
+
if (!fs9.existsSync(tarballPath)) {
|
|
2378
2420
|
throw new Error("Downloaded tarball not found");
|
|
2379
2421
|
}
|
|
2380
2422
|
execSync(`tar -xzf ${tarballPath} --strip-components=1`, {
|
|
@@ -2386,7 +2428,7 @@ async function downloadAndStageUpdate() {
|
|
|
2386
2428
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2387
2429
|
});
|
|
2388
2430
|
const cliPath = path13.join(versionDir, "dist", "cli.js");
|
|
2389
|
-
if (!
|
|
2431
|
+
if (!fs9.existsSync(cliPath)) {
|
|
2390
2432
|
throw new Error("CLI entry point not found in downloaded package");
|
|
2391
2433
|
}
|
|
2392
2434
|
stageUpdate(targetVersion, cliPath);
|
|
@@ -2394,7 +2436,7 @@ async function downloadAndStageUpdate() {
|
|
|
2394
2436
|
return { success: true, version: targetVersion };
|
|
2395
2437
|
} catch (error) {
|
|
2396
2438
|
try {
|
|
2397
|
-
|
|
2439
|
+
fs9.rmSync(versionDir, { recursive: true, force: true });
|
|
2398
2440
|
} catch {}
|
|
2399
2441
|
return {
|
|
2400
2442
|
success: false,
|
|
@@ -2472,7 +2514,7 @@ function performRollback() {
|
|
|
2472
2514
|
}
|
|
2473
2515
|
|
|
2474
2516
|
// src/core/daemon.ts
|
|
2475
|
-
import
|
|
2517
|
+
import fs10 from "node:fs";
|
|
2476
2518
|
import net from "node:net";
|
|
2477
2519
|
import { spawn as spawn2 } from "node:child_process";
|
|
2478
2520
|
|
|
@@ -2485,7 +2527,7 @@ import {
|
|
|
2485
2527
|
StdioClientTransport,
|
|
2486
2528
|
getDefaultEnvironment
|
|
2487
2529
|
} from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
2488
|
-
|
|
2530
|
+
import { StreamableHTTPClientTransport, StreamableHTTPError } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
2489
2531
|
var SERVER_VERSION = APP_VERSION;
|
|
2490
2532
|
var DEFAULT_UPSTREAM_TIMEOUT_MS = 60000;
|
|
2491
2533
|
var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
|
|
@@ -2653,10 +2695,7 @@ function parseNamespacedUri(uri) {
|
|
|
2653
2695
|
};
|
|
2654
2696
|
}
|
|
2655
2697
|
async function callUpstream(upstream, method, params, id, secrets, runtime, passthroughAuthorizationHeader) {
|
|
2656
|
-
|
|
2657
|
-
return callStdioUpstream(upstream, method, params, secrets, runtime);
|
|
2658
|
-
}
|
|
2659
|
-
return callHttpUpstream(upstream, method, params, id, secrets, passthroughAuthorizationHeader);
|
|
2698
|
+
return callUpstreamOnce(upstream, method, params, secrets, runtime, passthroughAuthorizationHeader, false);
|
|
2660
2699
|
}
|
|
2661
2700
|
async function getUpstreamTokenCounts(config, secrets, runtime) {
|
|
2662
2701
|
if (!runtime.tokenCache) {
|
|
@@ -2749,75 +2788,110 @@ function buildStdioServerParameters(spec, secrets) {
|
|
|
2749
2788
|
env: resolveStdioEnv(spec, secrets)
|
|
2750
2789
|
};
|
|
2751
2790
|
}
|
|
2752
|
-
async function
|
|
2791
|
+
async function buildHttpHeaders(upstream, secrets, options) {
|
|
2792
|
+
const headers = {};
|
|
2793
|
+
for (const [key, value] of Object.entries(upstream.spec.headers ?? {})) {
|
|
2794
|
+
if (key.toLowerCase() === "authorization" && isOAuthReference(value)) {
|
|
2795
|
+
const oauthServerName = oauthReferenceServerName(value);
|
|
2796
|
+
const accessToken = await getOAuthAccessToken(oauthServerName, upstream.spec, secrets, {
|
|
2797
|
+
forceRefresh: options.forceOAuthRefresh
|
|
2798
|
+
});
|
|
2799
|
+
headers[key] = `Bearer ${accessToken}`;
|
|
2800
|
+
} else {
|
|
2801
|
+
headers[key] = secrets.resolveMaybeSecret(value);
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
if (options.passthroughAuthorizationHeader) {
|
|
2805
|
+
headers.Authorization = options.passthroughAuthorizationHeader;
|
|
2806
|
+
}
|
|
2807
|
+
return headers;
|
|
2808
|
+
}
|
|
2809
|
+
async function closeUpstreamConnection(entry) {
|
|
2753
2810
|
try {
|
|
2754
2811
|
const connection = await entry.promise;
|
|
2755
2812
|
await connection.transport.close();
|
|
2756
2813
|
} catch {}
|
|
2757
2814
|
}
|
|
2758
|
-
async function
|
|
2815
|
+
async function reconcileUpstreamConnections(config, runtime) {
|
|
2759
2816
|
const activeSpecs = new Map(Object.entries(config.servers).filter(([, spec]) => isServerEnabled(spec)).map(([name, spec]) => [name, specFingerprint(spec)]));
|
|
2760
|
-
const
|
|
2761
|
-
for (const [
|
|
2762
|
-
const
|
|
2817
|
+
const staleKeys = [];
|
|
2818
|
+
for (const [key, entry] of runtime.upstreamConnections.entries()) {
|
|
2819
|
+
const upstreamName = key.includes(":") ? key.slice(0, key.indexOf(":")) : key;
|
|
2820
|
+
const expectedFingerprint = activeSpecs.get(upstreamName);
|
|
2763
2821
|
if (!expectedFingerprint || expectedFingerprint !== entry.fingerprint) {
|
|
2764
|
-
|
|
2822
|
+
staleKeys.push(key);
|
|
2765
2823
|
}
|
|
2766
2824
|
}
|
|
2767
|
-
for (const
|
|
2768
|
-
const entry = runtime.
|
|
2825
|
+
for (const key of staleKeys) {
|
|
2826
|
+
const entry = runtime.upstreamConnections.get(key);
|
|
2769
2827
|
if (!entry) {
|
|
2770
2828
|
continue;
|
|
2771
2829
|
}
|
|
2772
|
-
runtime.
|
|
2773
|
-
await
|
|
2830
|
+
runtime.upstreamConnections.delete(key);
|
|
2831
|
+
await closeUpstreamConnection(entry);
|
|
2774
2832
|
}
|
|
2775
2833
|
}
|
|
2776
|
-
function
|
|
2777
|
-
const
|
|
2834
|
+
function invalidateUpstreamConnection(upstreamName, passthroughAuthorizationHeader, runtime) {
|
|
2835
|
+
const key = passthroughAuthorizationHeader ? `${upstreamName}:passthrough` : upstreamName;
|
|
2836
|
+
const existing = runtime.upstreamConnections.get(key);
|
|
2778
2837
|
if (!existing) {
|
|
2779
2838
|
return;
|
|
2780
2839
|
}
|
|
2781
|
-
runtime.
|
|
2782
|
-
|
|
2840
|
+
runtime.upstreamConnections.delete(key);
|
|
2841
|
+
closeUpstreamConnection(existing);
|
|
2783
2842
|
}
|
|
2784
|
-
async function
|
|
2843
|
+
async function getUpstreamConnection(upstream, secrets, runtime, options = {}) {
|
|
2844
|
+
const { forceOAuthRefresh = false, passthroughAuthorizationHeader } = options;
|
|
2845
|
+
const cacheKey = passthroughAuthorizationHeader ? `${upstream.name}:passthrough` : upstream.name;
|
|
2785
2846
|
const fingerprint = specFingerprint(upstream.spec);
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
if (existing
|
|
2789
|
-
|
|
2847
|
+
if (!forceOAuthRefresh) {
|
|
2848
|
+
const existing = runtime.upstreamConnections.get(cacheKey);
|
|
2849
|
+
if (existing) {
|
|
2850
|
+
if (existing.fingerprint === fingerprint) {
|
|
2851
|
+
return existing.promise;
|
|
2852
|
+
}
|
|
2853
|
+
runtime.upstreamConnections.delete(cacheKey);
|
|
2854
|
+
closeUpstreamConnection(existing);
|
|
2790
2855
|
}
|
|
2791
|
-
runtime.stdioConnections.delete(upstream.name);
|
|
2792
|
-
closeStdioConnection(existing);
|
|
2793
2856
|
}
|
|
2794
2857
|
const promise = (async () => {
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2858
|
+
let transport;
|
|
2859
|
+
if (upstream.spec.transport === "stdio") {
|
|
2860
|
+
transport = new StdioClientTransport(buildStdioServerParameters(upstream.spec, secrets));
|
|
2861
|
+
} else {
|
|
2862
|
+
const headers = await buildHttpHeaders(upstream, secrets, { forceOAuthRefresh, passthroughAuthorizationHeader });
|
|
2863
|
+
const capturingFetch = async (url, init) => {
|
|
2864
|
+
const response = await fetch(url, init);
|
|
2865
|
+
const wwwAuth = response.headers.get("www-authenticate");
|
|
2866
|
+
if (wwwAuth) {
|
|
2867
|
+
if (!runtime.lastWwwAuthenticate) {
|
|
2868
|
+
runtime.lastWwwAuthenticate = new Map;
|
|
2869
|
+
}
|
|
2870
|
+
runtime.lastWwwAuthenticate.set(upstream.name, wwwAuth);
|
|
2871
|
+
}
|
|
2872
|
+
return response;
|
|
2873
|
+
};
|
|
2874
|
+
transport = new StreamableHTTPClientTransport(new URL2(upstream.spec.url), { requestInit: { headers }, fetch: capturingFetch });
|
|
2875
|
+
}
|
|
2876
|
+
const client = new Client({ name: "mcpx", version: SERVER_VERSION });
|
|
2800
2877
|
await withTimeout(client.connect(transport), DEFAULT_CONNECT_TIMEOUT_MS, `Upstream ${upstream.name} failed to connect within ${DEFAULT_CONNECT_TIMEOUT_MS}ms.`);
|
|
2801
|
-
return {
|
|
2802
|
-
fingerprint,
|
|
2803
|
-
client,
|
|
2804
|
-
transport
|
|
2805
|
-
};
|
|
2878
|
+
return { fingerprint, client, transport };
|
|
2806
2879
|
})();
|
|
2807
|
-
runtime.
|
|
2808
|
-
fingerprint,
|
|
2809
|
-
promise
|
|
2810
|
-
});
|
|
2880
|
+
runtime.upstreamConnections.set(cacheKey, { fingerprint, promise });
|
|
2811
2881
|
try {
|
|
2812
2882
|
return await promise;
|
|
2813
2883
|
} catch (error) {
|
|
2814
|
-
runtime.
|
|
2884
|
+
runtime.upstreamConnections.delete(cacheKey);
|
|
2815
2885
|
throw error;
|
|
2816
2886
|
}
|
|
2817
2887
|
}
|
|
2818
|
-
async function
|
|
2888
|
+
async function callUpstreamOnce(upstream, method, params, secrets, runtime, passthroughAuthorizationHeader, forceOAuthRefresh) {
|
|
2889
|
+
const hasOAuth = upstream.spec.transport === "http" && Object.values(upstream.spec.headers ?? {}).some(isOAuthReference);
|
|
2819
2890
|
try {
|
|
2820
|
-
const connection = await
|
|
2891
|
+
const connection = await getUpstreamConnection(upstream, secrets, runtime, {
|
|
2892
|
+
forceOAuthRefresh,
|
|
2893
|
+
passthroughAuthorizationHeader
|
|
2894
|
+
});
|
|
2821
2895
|
const timeoutMs = getConfiguredTimeoutMs();
|
|
2822
2896
|
const timeoutMessage = `Upstream ${upstream.name} timed out after ${timeoutMs}ms for method ${method}.`;
|
|
2823
2897
|
if (method === "tools/list") {
|
|
@@ -2838,161 +2912,17 @@ async function callStdioUpstream(upstream, method, params, secrets, runtime) {
|
|
|
2838
2912
|
if (method === "prompts/get") {
|
|
2839
2913
|
return withTimeout(connection.client.getPrompt(params), timeoutMs, timeoutMessage);
|
|
2840
2914
|
}
|
|
2841
|
-
throw new Error(`Unsupported
|
|
2915
|
+
throw new Error(`Unsupported method: ${method}`);
|
|
2842
2916
|
} catch (error) {
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
}
|
|
2847
|
-
async function callHttpUpstream(upstream, method, params, id, secrets, passthroughAuthorizationHeader) {
|
|
2848
|
-
return callHttpUpstreamOnce(upstream, method, params, id, secrets, passthroughAuthorizationHeader, false);
|
|
2849
|
-
}
|
|
2850
|
-
async function callHttpUpstreamOnce(upstream, method, params, id, secrets, passthroughAuthorizationHeader, forceOAuthRefresh) {
|
|
2851
|
-
const headers = {
|
|
2852
|
-
"content-type": "application/json",
|
|
2853
|
-
accept: "application/json, text/event-stream"
|
|
2854
|
-
};
|
|
2855
|
-
for (const [key, value] of Object.entries(upstream.spec.headers ?? {})) {
|
|
2856
|
-
if (key.toLowerCase() === "authorization" && isOAuthReference(value)) {
|
|
2857
|
-
const oauthServerName = oauthReferenceServerName(value);
|
|
2858
|
-
const accessToken = await getOAuthAccessToken(oauthServerName, upstream.spec, secrets, { forceRefresh: forceOAuthRefresh });
|
|
2859
|
-
headers[key] = `Bearer ${accessToken}`;
|
|
2860
|
-
} else {
|
|
2861
|
-
headers[key] = secrets.resolveMaybeSecret(value);
|
|
2917
|
+
invalidateUpstreamConnection(upstream.name, passthroughAuthorizationHeader, runtime);
|
|
2918
|
+
if (!forceOAuthRefresh && hasOAuth && error instanceof StreamableHTTPError && (error.code === 401 || error.code === 403)) {
|
|
2919
|
+
return callUpstreamOnce(upstream, method, params, secrets, runtime, passthroughAuthorizationHeader, true);
|
|
2862
2920
|
}
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
}
|
|
2867
|
-
const timeoutMs = getConfiguredTimeoutMs();
|
|
2868
|
-
const timeoutController = new AbortController;
|
|
2869
|
-
const timeoutHandle = setTimeout(() => timeoutController.abort(), timeoutMs);
|
|
2870
|
-
let response;
|
|
2871
|
-
try {
|
|
2872
|
-
response = await fetch(upstream.spec.url, {
|
|
2873
|
-
method: "POST",
|
|
2874
|
-
headers,
|
|
2875
|
-
body: JSON.stringify({
|
|
2876
|
-
jsonrpc: JSON_RPC_VERSION2,
|
|
2877
|
-
id,
|
|
2878
|
-
method,
|
|
2879
|
-
params
|
|
2880
|
-
}),
|
|
2881
|
-
signal: timeoutController.signal
|
|
2882
|
-
});
|
|
2883
|
-
} catch (error) {
|
|
2884
|
-
const name = error.name;
|
|
2885
|
-
if (name === "AbortError") {
|
|
2886
|
-
throw new Error(`Upstream ${upstream.name} timed out after ${timeoutMs}ms for method ${method}.`);
|
|
2921
|
+
if (upstream.spec.transport === "http" && error instanceof StreamableHTTPError && error.code) {
|
|
2922
|
+
const wwwAuthenticate = runtime.lastWwwAuthenticate?.get(upstream.name);
|
|
2923
|
+
throw new UpstreamHttpError(upstream.name, error.code, error.message ?? "", wwwAuthenticate);
|
|
2887
2924
|
}
|
|
2888
2925
|
throw error;
|
|
2889
|
-
} finally {
|
|
2890
|
-
clearTimeout(timeoutHandle);
|
|
2891
|
-
}
|
|
2892
|
-
if (!response.ok) {
|
|
2893
|
-
const responseText = await response.text();
|
|
2894
|
-
if (!forceOAuthRefresh && (response.status === 401 || response.status === 403) && Object.values(upstream.spec.headers ?? {}).some((value) => isOAuthReference(value))) {
|
|
2895
|
-
return callHttpUpstreamOnce(upstream, method, params, id, secrets, passthroughAuthorizationHeader, true);
|
|
2896
|
-
}
|
|
2897
|
-
throw new UpstreamHttpError(upstream.name, response.status, responseText, response.headers.get("www-authenticate") ?? undefined);
|
|
2898
|
-
}
|
|
2899
|
-
let payload = null;
|
|
2900
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
2901
|
-
if (contentType.includes("application/json")) {
|
|
2902
|
-
payload = JSON.parse(await response.text());
|
|
2903
|
-
} else if (contentType.includes("text/event-stream")) {
|
|
2904
|
-
payload = await readSseJsonRpcResponse(response, id);
|
|
2905
|
-
} else {
|
|
2906
|
-
try {
|
|
2907
|
-
payload = JSON.parse(await response.text());
|
|
2908
|
-
} catch {
|
|
2909
|
-
payload = null;
|
|
2910
|
-
}
|
|
2911
|
-
}
|
|
2912
|
-
if (!payload) {
|
|
2913
|
-
throw new Error(`Upstream ${upstream.name} response could not be parsed as JSON-RPC payload.`);
|
|
2914
|
-
}
|
|
2915
|
-
if (payload.error) {
|
|
2916
|
-
throw new Error(`Upstream ${upstream.name} error: ${payload.error.message}`);
|
|
2917
|
-
}
|
|
2918
|
-
return payload.result;
|
|
2919
|
-
}
|
|
2920
|
-
async function readSseJsonRpcResponse(response, expectedId) {
|
|
2921
|
-
if (!response.body) {
|
|
2922
|
-
return null;
|
|
2923
|
-
}
|
|
2924
|
-
const decoder = new TextDecoder;
|
|
2925
|
-
const reader = response.body.getReader();
|
|
2926
|
-
let buffer = "";
|
|
2927
|
-
let dataLines = [];
|
|
2928
|
-
let latestPayload = null;
|
|
2929
|
-
const consumeEvent = () => {
|
|
2930
|
-
if (dataLines.length === 0) {
|
|
2931
|
-
return null;
|
|
2932
|
-
}
|
|
2933
|
-
const combined = dataLines.join(`
|
|
2934
|
-
`).trim();
|
|
2935
|
-
dataLines = [];
|
|
2936
|
-
if (!combined || combined === "[DONE]") {
|
|
2937
|
-
return null;
|
|
2938
|
-
}
|
|
2939
|
-
try {
|
|
2940
|
-
const parsed = JSON.parse(combined);
|
|
2941
|
-
latestPayload = parsed;
|
|
2942
|
-
const parsedId = parsed.id ?? null;
|
|
2943
|
-
if (parsedId === expectedId) {
|
|
2944
|
-
return parsed;
|
|
2945
|
-
}
|
|
2946
|
-
return null;
|
|
2947
|
-
} catch {
|
|
2948
|
-
return null;
|
|
2949
|
-
}
|
|
2950
|
-
};
|
|
2951
|
-
try {
|
|
2952
|
-
while (true) {
|
|
2953
|
-
const { value, done } = await reader.read();
|
|
2954
|
-
if (done) {
|
|
2955
|
-
break;
|
|
2956
|
-
}
|
|
2957
|
-
buffer += decoder.decode(value, { stream: true });
|
|
2958
|
-
while (true) {
|
|
2959
|
-
const newlineIndex = buffer.indexOf(`
|
|
2960
|
-
`);
|
|
2961
|
-
if (newlineIndex < 0) {
|
|
2962
|
-
break;
|
|
2963
|
-
}
|
|
2964
|
-
let line = buffer.slice(0, newlineIndex);
|
|
2965
|
-
buffer = buffer.slice(newlineIndex + 1);
|
|
2966
|
-
if (line.endsWith("\r")) {
|
|
2967
|
-
line = line.slice(0, -1);
|
|
2968
|
-
}
|
|
2969
|
-
if (line.startsWith("data:")) {
|
|
2970
|
-
dataLines.push(line.slice("data:".length).trimStart());
|
|
2971
|
-
continue;
|
|
2972
|
-
}
|
|
2973
|
-
if (line === "") {
|
|
2974
|
-
const matched = consumeEvent();
|
|
2975
|
-
if (matched) {
|
|
2976
|
-
return matched;
|
|
2977
|
-
}
|
|
2978
|
-
}
|
|
2979
|
-
}
|
|
2980
|
-
}
|
|
2981
|
-
buffer += decoder.decode();
|
|
2982
|
-
if (buffer.length > 0) {
|
|
2983
|
-
let finalLine = buffer;
|
|
2984
|
-
if (finalLine.endsWith("\r")) {
|
|
2985
|
-
finalLine = finalLine.slice(0, -1);
|
|
2986
|
-
}
|
|
2987
|
-
if (finalLine.startsWith("data:")) {
|
|
2988
|
-
dataLines.push(finalLine.slice("data:".length).trimStart());
|
|
2989
|
-
}
|
|
2990
|
-
}
|
|
2991
|
-
return consumeEvent() ?? latestPayload;
|
|
2992
|
-
} finally {
|
|
2993
|
-
try {
|
|
2994
|
-
await reader.cancel();
|
|
2995
|
-
} catch {}
|
|
2996
2926
|
}
|
|
2997
2927
|
}
|
|
2998
2928
|
function authHeaderIsValid(request, expectedToken) {
|
|
@@ -3255,7 +3185,7 @@ async function handleRequestObject(request, secrets, runtime, upstreamFilter, cl
|
|
|
3255
3185
|
if (upstreamFilter && listUpstreams(config, upstreamFilter).length === 0) {
|
|
3256
3186
|
return makeError(id, -32602, `Unknown upstream: ${upstreamFilter}`);
|
|
3257
3187
|
}
|
|
3258
|
-
await
|
|
3188
|
+
await reconcileUpstreamConnections(config, runtime);
|
|
3259
3189
|
if (request.method === "custom/tokenCounts") {
|
|
3260
3190
|
const result = await getUpstreamTokenCounts(config, secrets, runtime);
|
|
3261
3191
|
return makeResult(id, result);
|
|
@@ -3351,7 +3281,7 @@ async function maybeHandleWellKnownOAuthRequest(request, response, requestUrl, s
|
|
|
3351
3281
|
function createGatewayServer(options) {
|
|
3352
3282
|
const debug = process.env.MCPX_GATEWAY_DEBUG === "1";
|
|
3353
3283
|
const runtime = {
|
|
3354
|
-
|
|
3284
|
+
upstreamConnections: new Map,
|
|
3355
3285
|
upstreamErrors: new Map
|
|
3356
3286
|
};
|
|
3357
3287
|
const server = http2.createServer(async (request, response) => {
|
|
@@ -3517,10 +3447,10 @@ function createGatewayServer(options) {
|
|
|
3517
3447
|
}
|
|
3518
3448
|
});
|
|
3519
3449
|
server.on("close", () => {
|
|
3520
|
-
for (const entry of runtime.
|
|
3521
|
-
|
|
3450
|
+
for (const entry of runtime.upstreamConnections.values()) {
|
|
3451
|
+
closeUpstreamConnection(entry);
|
|
3522
3452
|
}
|
|
3523
|
-
runtime.
|
|
3453
|
+
runtime.upstreamConnections.clear();
|
|
3524
3454
|
});
|
|
3525
3455
|
server.listen(options.port, "127.0.0.1");
|
|
3526
3456
|
return server;
|
|
@@ -3528,10 +3458,10 @@ function createGatewayServer(options) {
|
|
|
3528
3458
|
|
|
3529
3459
|
// src/core/daemon.ts
|
|
3530
3460
|
function readPidFromFile(pidPath = getPidPath()) {
|
|
3531
|
-
if (!
|
|
3461
|
+
if (!fs10.existsSync(pidPath)) {
|
|
3532
3462
|
return null;
|
|
3533
3463
|
}
|
|
3534
|
-
const raw =
|
|
3464
|
+
const raw = fs10.readFileSync(pidPath, "utf8").trim();
|
|
3535
3465
|
if (!raw) {
|
|
3536
3466
|
return null;
|
|
3537
3467
|
}
|
|
@@ -3633,7 +3563,7 @@ async function startDaemon(config, cliPath, secrets) {
|
|
|
3633
3563
|
};
|
|
3634
3564
|
}
|
|
3635
3565
|
try {
|
|
3636
|
-
|
|
3566
|
+
fs10.unlinkSync(getPidPath());
|
|
3637
3567
|
} catch {}
|
|
3638
3568
|
}
|
|
3639
3569
|
const port = await resolveGatewayPort(config);
|
|
@@ -3642,7 +3572,7 @@ async function startDaemon(config, cliPath, secrets) {
|
|
|
3642
3572
|
const logPath = getLogPath();
|
|
3643
3573
|
ensureParentDir(pidPath);
|
|
3644
3574
|
ensureParentDir(logPath);
|
|
3645
|
-
const logFd =
|
|
3575
|
+
const logFd = fs10.openSync(logPath, "a", 384);
|
|
3646
3576
|
const child = spawn2(process.execPath, [cliPath, "daemon", "run", "--port", String(port)], {
|
|
3647
3577
|
detached: true,
|
|
3648
3578
|
stdio: ["ignore", logFd, logFd],
|
|
@@ -3652,7 +3582,7 @@ async function startDaemon(config, cliPath, secrets) {
|
|
|
3652
3582
|
}
|
|
3653
3583
|
});
|
|
3654
3584
|
child.unref();
|
|
3655
|
-
|
|
3585
|
+
fs10.writeFileSync(pidPath, `${child.pid}
|
|
3656
3586
|
`, { mode: 384 });
|
|
3657
3587
|
startBackgroundUpdateCheck();
|
|
3658
3588
|
await waitForGatewayReady(port, token);
|
|
@@ -3676,7 +3606,7 @@ function stopDaemon() {
|
|
|
3676
3606
|
process.kill(pid, "SIGTERM");
|
|
3677
3607
|
} catch {}
|
|
3678
3608
|
try {
|
|
3679
|
-
|
|
3609
|
+
fs10.unlinkSync(pidPath);
|
|
3680
3610
|
} catch {}
|
|
3681
3611
|
return {
|
|
3682
3612
|
stopped: true,
|
|
@@ -3689,10 +3619,10 @@ async function restartDaemon(config, cliPath, secrets) {
|
|
|
3689
3619
|
}
|
|
3690
3620
|
function readDaemonLogs(maxLines = 200) {
|
|
3691
3621
|
const logPath = getLogPath();
|
|
3692
|
-
if (!
|
|
3622
|
+
if (!fs10.existsSync(logPath)) {
|
|
3693
3623
|
return "";
|
|
3694
3624
|
}
|
|
3695
|
-
const lines =
|
|
3625
|
+
const lines = fs10.readFileSync(logPath, "utf8").split(`
|
|
3696
3626
|
`);
|
|
3697
3627
|
return lines.slice(Math.max(lines.length - maxLines, 0)).join(`
|
|
3698
3628
|
`);
|
|
@@ -3809,11 +3739,11 @@ async function buildStatusReport(config, managedIndex, daemon = getDaemonStatus(
|
|
|
3809
3739
|
tokenCount
|
|
3810
3740
|
};
|
|
3811
3741
|
if (serverEntry.enabled && tokenCount) {
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3742
|
+
totalGlobalTokens += tokenCount.total;
|
|
3743
|
+
for (const projectEntry of projectEntries) {
|
|
3744
|
+
if (!(projectEntry.disabledServers ?? []).includes(name)) {
|
|
3745
|
+
totalProjectTokens[projectEntry.path] = (totalProjectTokens[projectEntry.path] ?? 0) + tokenCount.total;
|
|
3746
|
+
}
|
|
3817
3747
|
}
|
|
3818
3748
|
}
|
|
3819
3749
|
return serverEntry;
|