@sunshaobei/dsh-mcp-manager 0.4.0 → 0.6.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.en.md +6 -0
- package/README.md +6 -0
- package/lib/client/float/float.d.ts +2 -2
- package/lib/client/float/panel.d.ts +2 -0
- package/lib/client/float/servers.d.ts +6 -0
- package/lib/client.js +137 -236
- package/lib/index.js +779 -516
- package/lib/server/api/routes.d.ts +2 -2
- package/lib/server/catalog/search.d.ts +3 -3
- package/lib/server/config/sources-sync.d.ts +2 -2
- package/lib/server/connection/interface.d.ts +9 -0
- package/lib/server/connection/orchestrator/manager.d.ts +28 -10
- package/lib/server/connection/runtime/deps.d.ts +1 -0
- package/lib/server/connection/runtime/middleware.d.ts +3 -1
- package/package.json +3 -4
package/lib/index.js
CHANGED
|
@@ -590,132 +590,138 @@ var init_sources = __esm({
|
|
|
590
590
|
}
|
|
591
591
|
});
|
|
592
592
|
|
|
593
|
-
// lib/
|
|
594
|
-
function
|
|
595
|
-
if (typeof value !== "number" || !Number.isFinite(value))
|
|
596
|
-
|
|
597
|
-
|
|
593
|
+
// lib/server/config/normalize.js
|
|
594
|
+
function assertReconnectDelay(value, path) {
|
|
595
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0 || value > MAX_TIMER_DELAY_MS) {
|
|
596
|
+
throw new Error(`${path} must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`);
|
|
597
|
+
}
|
|
598
598
|
}
|
|
599
|
-
function
|
|
600
|
-
|
|
599
|
+
function assertReconnectRelation(out) {
|
|
600
|
+
const initialDelayMs = out.initialDelayMs ?? RECONNECT_DEFAULTS.initialDelayMs;
|
|
601
|
+
const maxDelayMs = out.maxDelayMs ?? RECONNECT_DEFAULTS.maxDelayMs;
|
|
602
|
+
if (initialDelayMs > maxDelayMs) {
|
|
603
|
+
throw new Error("reconnect.initialDelayMs must be less than or equal to maxDelayMs");
|
|
604
|
+
}
|
|
605
|
+
const { maxAttempts } = out;
|
|
606
|
+
if (maxAttempts !== void 0 && (typeof maxAttempts !== "number" || !Number.isInteger(maxAttempts) || maxAttempts < 1)) {
|
|
607
|
+
throw new Error("reconnect.maxAttempts must be a positive integer");
|
|
608
|
+
}
|
|
601
609
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
610
|
+
function normalizeReconnect(raw) {
|
|
611
|
+
if (raw === void 0 || raw === null)
|
|
612
|
+
return {};
|
|
613
|
+
if (typeof raw !== "object" || Array.isArray(raw))
|
|
614
|
+
throw new Error("reconnect must be an object");
|
|
615
|
+
const out = {};
|
|
616
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
617
|
+
if (RECONNECT_KEYS.has(key))
|
|
618
|
+
out[key] = value;
|
|
609
619
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
reconnecting: "reconnecting",
|
|
621
|
-
disabled: "disabled",
|
|
622
|
-
stopped: "stopped",
|
|
623
|
-
failed: "failed"
|
|
624
|
-
};
|
|
625
|
-
EMPTY_STATUS_COUNTS = {
|
|
626
|
-
connected: 0,
|
|
627
|
-
connecting: 0,
|
|
628
|
-
reconnecting: 0,
|
|
629
|
-
disabled: 0,
|
|
630
|
-
stopped: 0,
|
|
631
|
-
failed: 0
|
|
632
|
-
};
|
|
620
|
+
assertReconnectFields(out);
|
|
621
|
+
return out;
|
|
622
|
+
}
|
|
623
|
+
function assertReconnectFields(out) {
|
|
624
|
+
if (out.initialDelayMs !== void 0)
|
|
625
|
+
assertReconnectDelay(out.initialDelayMs, "reconnect.initialDelayMs");
|
|
626
|
+
if (out.maxDelayMs !== void 0)
|
|
627
|
+
assertReconnectDelay(out.maxDelayMs, "reconnect.maxDelayMs");
|
|
628
|
+
if (out.enabled !== void 0 && typeof out.enabled !== "boolean") {
|
|
629
|
+
throw new Error("reconnect.enabled must be a boolean");
|
|
633
630
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
summary: "summary",
|
|
643
|
-
uiConfigChanged: "ui-config-changed",
|
|
644
|
-
ping: "ping"
|
|
645
|
-
};
|
|
631
|
+
assertReconnectRelation(out);
|
|
632
|
+
}
|
|
633
|
+
function assertTransportRequired(src, transport) {
|
|
634
|
+
if (transport === "stdio") {
|
|
635
|
+
if (typeof src.command !== "string" || src.command.trim() === "") {
|
|
636
|
+
throw new Error("stdio server requires a command");
|
|
637
|
+
}
|
|
638
|
+
return;
|
|
646
639
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
// lib/shared/routes.js
|
|
650
|
-
var ROUTES, ROUTE_FENCE;
|
|
651
|
-
var init_routes = __esm({
|
|
652
|
-
"lib/shared/routes.js"() {
|
|
653
|
-
"use strict";
|
|
654
|
-
ROUTES = {
|
|
655
|
-
servers: "/api/dsh-mcp/servers",
|
|
656
|
-
config: "/api/dsh-mcp/config",
|
|
657
|
-
session: "/api/dsh-mcp/session",
|
|
658
|
-
resume: "/api/dsh-mcp/resume",
|
|
659
|
-
connect: "/api/dsh-mcp/servers/connect",
|
|
660
|
-
disconnect: "/api/dsh-mcp/servers/disconnect",
|
|
661
|
-
reconnect: "/api/dsh-mcp/servers/reconnect",
|
|
662
|
-
importJson: "/api/dsh-mcp/import/json",
|
|
663
|
-
sources: "/api/dsh-mcp/sources",
|
|
664
|
-
sourceSave: "/api/dsh-mcp/sources/save",
|
|
665
|
-
sourceActivate: "/api/dsh-mcp/sources/activate",
|
|
666
|
-
events: "/api/dsh-mcp/events",
|
|
667
|
-
health: "/api/dsh-mcp/health",
|
|
668
|
-
toolDisable: "/api/dsh-mcp/tool-disable"
|
|
669
|
-
};
|
|
670
|
-
ROUTE_FENCE = {
|
|
671
|
-
servers: { guarded: ["GET", "POST", "PATCH", "DELETE"], loopbackExempt: [] },
|
|
672
|
-
config: { guarded: ["POST"], loopbackExempt: ["GET"] },
|
|
673
|
-
session: { guarded: ["POST"], loopbackExempt: [] },
|
|
674
|
-
resume: { guarded: ["POST"], loopbackExempt: [] },
|
|
675
|
-
connect: { guarded: ["POST"], loopbackExempt: [] },
|
|
676
|
-
disconnect: { guarded: ["POST"], loopbackExempt: [] },
|
|
677
|
-
reconnect: { guarded: ["POST"], loopbackExempt: [] },
|
|
678
|
-
importJson: { guarded: ["POST"], loopbackExempt: [] },
|
|
679
|
-
sources: { guarded: ["GET", "POST"], loopbackExempt: [] },
|
|
680
|
-
sourceSave: { guarded: ["POST"], loopbackExempt: [] },
|
|
681
|
-
sourceActivate: { guarded: ["POST"], loopbackExempt: [] },
|
|
682
|
-
events: { guarded: ["GET"], loopbackExempt: [] },
|
|
683
|
-
health: { guarded: ["GET"], loopbackExempt: [] },
|
|
684
|
-
toolDisable: { guarded: ["PATCH"], loopbackExempt: [] }
|
|
685
|
-
};
|
|
640
|
+
if (typeof src.url !== "string" || src.url.trim() === "") {
|
|
641
|
+
throw new Error("streamable-http server requires a url");
|
|
686
642
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
"lib/shared/constants.js"() {
|
|
693
|
-
"use strict";
|
|
694
|
-
SCOPE_GLOBAL = "global";
|
|
695
|
-
SCOPE_PROJECT = "project";
|
|
696
|
-
MIDDLEWARE_GLOBAL_ROOT = "@global";
|
|
697
|
-
MCP_MANAGER_BUNDLE_PACKAGE = "dsh-mcp-manager";
|
|
698
|
-
MCP_MANAGER_ROW_ID = "dsh-mcp-manager";
|
|
699
|
-
MCP_MANAGER_SETTINGS_NAMESPACE = "dsh-mcp-manager";
|
|
700
|
-
MCP_MANAGER_IDENTITY = Object.freeze({
|
|
701
|
-
bundlePackage: MCP_MANAGER_BUNDLE_PACKAGE,
|
|
702
|
-
rowId: MCP_MANAGER_ROW_ID,
|
|
703
|
-
settingsNamespace: MCP_MANAGER_SETTINGS_NAMESPACE,
|
|
704
|
-
rowConfigKey: `${MCP_MANAGER_BUNDLE_PACKAGE}#${MCP_MANAGER_ROW_ID}`
|
|
705
|
-
});
|
|
643
|
+
let parsed;
|
|
644
|
+
try {
|
|
645
|
+
parsed = new URL(src.url);
|
|
646
|
+
} catch {
|
|
647
|
+
throw new Error(`invalid url: ${src.url}`);
|
|
706
648
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
649
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
650
|
+
throw new Error(`unsupported protocol: ${parsed.protocol}//`);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
function applyTransportFields(server, src, transport) {
|
|
654
|
+
if (transport === "stdio") {
|
|
655
|
+
server.command = src.command;
|
|
656
|
+
if (Array.isArray(src.args))
|
|
657
|
+
server.args = src.args.map(String);
|
|
658
|
+
if (typeof src.cwd === "string" && src.cwd !== "")
|
|
659
|
+
server.cwd = src.cwd;
|
|
660
|
+
if (typeof src.env === "object" && src.env !== null) {
|
|
661
|
+
server.env = Object.fromEntries(Object.entries(src.env).map(([key, value]) => [
|
|
662
|
+
key,
|
|
663
|
+
String(value)
|
|
664
|
+
]));
|
|
665
|
+
}
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
server.url = src.url;
|
|
669
|
+
if (typeof src.headers === "object" && src.headers !== null) {
|
|
670
|
+
server.headers = Object.fromEntries(Object.entries(src.headers).map(([key, value]) => [
|
|
671
|
+
key,
|
|
672
|
+
String(value)
|
|
673
|
+
]));
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function normalizeToolCallTimeoutMs(value) {
|
|
677
|
+
return typeof value === "number" && value > 0 ? Math.floor(value) : DEFAULT_TOOL_CALL_TIMEOUT_MS;
|
|
678
|
+
}
|
|
679
|
+
function normalizeDescription(value) {
|
|
680
|
+
if (typeof value !== "string")
|
|
681
|
+
return void 0;
|
|
682
|
+
const trimmed = value.trim();
|
|
683
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
684
|
+
}
|
|
685
|
+
function normalizeServer(input) {
|
|
686
|
+
if (typeof input !== "object" || input === null)
|
|
687
|
+
throw new Error("server config must be an object");
|
|
688
|
+
const src = input;
|
|
689
|
+
const name2 = String(src.name ?? "").trim();
|
|
690
|
+
if (!SERVER_NAME_PATTERN.test(name2)) {
|
|
691
|
+
throw new Error(`server name must match ${SERVER_NAME_PATTERN.source}`);
|
|
692
|
+
}
|
|
693
|
+
const transport = src.transport === "streamable-http" ? "streamable-http" : src.transport === "stdio" ? "stdio" : void 0;
|
|
694
|
+
if (transport === void 0)
|
|
695
|
+
throw new Error('transport must be "stdio" or "streamable-http"');
|
|
696
|
+
assertTransportRequired(src, transport);
|
|
697
|
+
const server = {
|
|
698
|
+
name: name2,
|
|
699
|
+
transport,
|
|
700
|
+
enabled: src.enabled !== false,
|
|
701
|
+
toolCallTimeoutMs: normalizeToolCallTimeoutMs(src.toolCallTimeoutMs),
|
|
702
|
+
reconnect: normalizeReconnect(src.reconnect),
|
|
703
|
+
// 能力目录的自定义描述(用户手写;MCP 协议无服务器级自描述,完整保留不截断)。
|
|
704
|
+
description: normalizeDescription(src.description)
|
|
705
|
+
};
|
|
706
|
+
if (typeof src.source === "string" && /^[a-z-]+$/.test(src.source)) {
|
|
707
|
+
server.source = src.source;
|
|
708
|
+
}
|
|
709
|
+
applyTransportFields(server, src, transport);
|
|
710
|
+
return server;
|
|
711
|
+
}
|
|
712
|
+
var RECONNECT_KEYS, MAX_TIMER_DELAY_MS, RECONNECT_DEFAULTS;
|
|
713
|
+
var init_normalize = __esm({
|
|
714
|
+
"lib/server/config/normalize.js"() {
|
|
712
715
|
"use strict";
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
716
|
+
init_interface();
|
|
717
|
+
RECONNECT_KEYS = /* @__PURE__ */ new Set(["enabled", "initialDelayMs", "maxDelayMs", "maxAttempts"]);
|
|
718
|
+
MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
719
|
+
RECONNECT_DEFAULTS = {
|
|
720
|
+
enabled: true,
|
|
721
|
+
initialDelayMs: 500,
|
|
722
|
+
maxDelayMs: 3e4,
|
|
723
|
+
maxAttempts: 10
|
|
724
|
+
};
|
|
719
725
|
}
|
|
720
726
|
});
|
|
721
727
|
|
|
@@ -725,7 +731,7 @@ __export(sources_sync_exports, {
|
|
|
725
731
|
collectParsedSources: () => collectParsedSources,
|
|
726
732
|
syncSourcesIntoManager: () => syncSourcesIntoManager
|
|
727
733
|
});
|
|
728
|
-
import {
|
|
734
|
+
import { isDeepStrictEqual } from "node:util";
|
|
729
735
|
function collectParsedSources(projectRoot) {
|
|
730
736
|
return SOURCE_IDS.map((id) => {
|
|
731
737
|
const path = sourcePath(id, projectRoot);
|
|
@@ -735,10 +741,15 @@ function collectParsedSources(projectRoot) {
|
|
|
735
741
|
async function syncSourcesIntoManager(manager, projectRoot, activate = /* @__PURE__ */ new Map()) {
|
|
736
742
|
const parsed = collectParsedSources(projectRoot);
|
|
737
743
|
const effective = resolveEffective(parsed, activate);
|
|
744
|
+
for (const [name2, item] of effective) {
|
|
745
|
+
if (sourceScope(item.source) !== (projectRoot === void 0 ? "global" : "project"))
|
|
746
|
+
effective.delete(name2);
|
|
747
|
+
}
|
|
738
748
|
const summary = { effective: [], removed: [], warnings: [] };
|
|
739
749
|
let projectStore;
|
|
740
750
|
try {
|
|
741
|
-
|
|
751
|
+
if (projectRoot !== void 0)
|
|
752
|
+
projectStore = await manager.projectStoreOrThrow();
|
|
742
753
|
} catch {
|
|
743
754
|
summary.warnings.push("无项目会话:项目来源本轮未落运行面(连接项目会话后重试)");
|
|
744
755
|
}
|
|
@@ -750,36 +761,24 @@ async function syncSourcesIntoManager(manager, projectRoot, activate = /* @__PUR
|
|
|
750
761
|
continue;
|
|
751
762
|
}
|
|
752
763
|
try {
|
|
753
|
-
item.config
|
|
764
|
+
item.config = normalizeServer({ ...item.config, source: item.source });
|
|
754
765
|
const existing = store.find(name2);
|
|
755
766
|
if (existing === void 0) {
|
|
756
767
|
await manager.add(item.config, scope);
|
|
757
|
-
} else
|
|
758
|
-
|
|
768
|
+
} else {
|
|
769
|
+
const merged = {
|
|
770
|
+
...item.config,
|
|
771
|
+
...existing.enabled !== void 0 ? { enabled: existing.enabled } : {}
|
|
772
|
+
};
|
|
773
|
+
if (!isDeepStrictEqual(existing, merged)) {
|
|
774
|
+
await manager.update(name2, merged, scope);
|
|
775
|
+
}
|
|
759
776
|
}
|
|
760
777
|
summary.effective.push({ name: name2, source: item.source, scope, shadowed: item.shadowed });
|
|
761
778
|
} catch (error) {
|
|
762
779
|
summary.warnings.push(`${name2}(${item.source})落运行面失败:${error instanceof Error ? error.message : String(error)}`);
|
|
763
780
|
}
|
|
764
781
|
}
|
|
765
|
-
for (const [name2, item] of effective) {
|
|
766
|
-
const winnerScope = sourceScope(item.source);
|
|
767
|
-
for (const loserScope of ["global", "project"]) {
|
|
768
|
-
if (loserScope === winnerScope)
|
|
769
|
-
continue;
|
|
770
|
-
const store = loserScope === "global" ? manager.store : projectStore;
|
|
771
|
-
if (store === void 0)
|
|
772
|
-
continue;
|
|
773
|
-
if (store.find(name2) === void 0)
|
|
774
|
-
continue;
|
|
775
|
-
try {
|
|
776
|
-
await manager.remove(name2, loserScope);
|
|
777
|
-
} catch {
|
|
778
|
-
}
|
|
779
|
-
store.remove(name2);
|
|
780
|
-
summary.removed.push({ name: name2, scope: loserScope });
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
782
|
const staleSweep = async (store, scope) => {
|
|
784
783
|
for (const server of [...store.data.servers]) {
|
|
785
784
|
if (effective.has(server.name))
|
|
@@ -792,18 +791,50 @@ async function syncSourcesIntoManager(manager, projectRoot, activate = /* @__PUR
|
|
|
792
791
|
summary.removed.push({ name: server.name, scope });
|
|
793
792
|
}
|
|
794
793
|
};
|
|
795
|
-
|
|
794
|
+
if (projectRoot === void 0)
|
|
795
|
+
await staleSweep(manager.store, "global");
|
|
796
796
|
if (projectStore !== void 0)
|
|
797
797
|
await staleSweep(projectStore, "project");
|
|
798
798
|
try {
|
|
799
|
-
const
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
799
|
+
const globalNames = new Set([...effective.values()].filter((i) => sourceScope(i.source) === "global").map((i) => i.config.name));
|
|
800
|
+
const projectNames = /* @__PURE__ */ new Set();
|
|
801
|
+
try {
|
|
802
|
+
const current = projectStore ?? await manager.projectStoreOrThrow();
|
|
803
|
+
for (const server of current.data.servers)
|
|
804
|
+
projectNames.add(server.name);
|
|
805
|
+
} catch {
|
|
806
|
+
}
|
|
807
|
+
if (projectRoot === void 0 && manager.sweepOrphanUserDisabled !== void 0) {
|
|
808
|
+
await manager.sweepOrphanUserDisabled(globalNames, projectNames);
|
|
809
|
+
}
|
|
810
|
+
} catch (error) {
|
|
811
|
+
summary.warnings.push(`孤儿禁用痕清扫失败:${error instanceof Error ? error.message : String(error)}`);
|
|
812
|
+
}
|
|
813
|
+
try {
|
|
814
|
+
const { readTextFile: readTextFile2, writeFileAtomic: writeFileAtomic2 } = await Promise.resolve().then(() => (init_interface(), interface_exports));
|
|
815
|
+
const writeIfChanged = async (path, servers) => {
|
|
816
|
+
const text = `${JSON.stringify({ version: 1, servers }, null, 2)}
|
|
817
|
+
`;
|
|
818
|
+
const previous = await readTextFile2(path);
|
|
819
|
+
if (previous !== null) {
|
|
820
|
+
try {
|
|
821
|
+
if (isDeepStrictEqual(JSON.parse(previous), JSON.parse(text)))
|
|
822
|
+
return;
|
|
823
|
+
} catch {
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
await writeFileAtomic2(path, text);
|
|
827
|
+
};
|
|
828
|
+
const withRuntimeEnabled = (config, store) => {
|
|
829
|
+
const cur = store.find(String(config.name));
|
|
830
|
+
return cur?.enabled !== void 0 ? { ...config, enabled: cur.enabled } : config;
|
|
831
|
+
};
|
|
832
|
+
const globalServers = [...effective.values()].filter((item) => sourceScope(item.source) === "global").map((item) => withRuntimeEnabled(item.config, manager.store));
|
|
833
|
+
if (projectRoot === void 0)
|
|
834
|
+
await writeIfChanged(manager.store.path, globalServers);
|
|
803
835
|
if (projectRoot !== void 0 && projectStore !== void 0) {
|
|
804
|
-
const projectServers = [...effective.values()].filter((item) => sourceScope(item.source) === "project").map((item) => item.config);
|
|
805
|
-
await
|
|
806
|
-
`);
|
|
836
|
+
const projectServers = [...effective.values()].filter((item) => sourceScope(item.source) === "project").map((item) => withRuntimeEnabled(item.config, projectStore));
|
|
837
|
+
await writeIfChanged(projectStore.path, projectServers);
|
|
807
838
|
}
|
|
808
839
|
} catch (error) {
|
|
809
840
|
summary.warnings.push(`生效配置回写失败:${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -813,89 +844,13 @@ async function syncSourcesIntoManager(manager, projectRoot, activate = /* @__PUR
|
|
|
813
844
|
} catch (error) {
|
|
814
845
|
summary.warnings.push(`refreshFromDisk 失败:${error instanceof Error ? error.message : String(error)}`);
|
|
815
846
|
}
|
|
816
|
-
try {
|
|
817
|
-
const { appendFileSync, mkdirSync: mkdirSync3 } = await import("node:fs");
|
|
818
|
-
const { homedir: homedir3 } = await import("node:os");
|
|
819
|
-
const logPath = join5(homedir3(), ".dsh", "dsh-mcp-manager", "sync-debug.log");
|
|
820
|
-
mkdirSync3(dirname5(logPath), { recursive: true });
|
|
821
|
-
const stamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
822
|
-
const lines = [
|
|
823
|
-
`[${stamp}] projectRoot=${projectRoot ?? "(none)"}`,
|
|
824
|
-
...parsed.map((p2) => ` parse ${p2.id}: exists=${p2.exists} servers=[${p2.servers.map((s2) => s2.name).join(",")}]${p2.error ? ` ERROR=${p2.error}` : ""}`),
|
|
825
|
-
...summary.effective.map((e2) => ` effective ${e2.name} <- ${e2.source} (shadowed: ${e2.shadowed.join(",") || "-"})`),
|
|
826
|
-
...summary.removed.map((r2) => ` removed ${r2.name} @ ${r2.scope}`),
|
|
827
|
-
...summary.warnings.map((w2) => ` WARN ${w2}`)
|
|
828
|
-
];
|
|
829
|
-
appendFileSync(logPath, lines.join("\n") + "\n");
|
|
830
|
-
} catch {
|
|
831
|
-
}
|
|
832
847
|
return summary;
|
|
833
848
|
}
|
|
834
849
|
var init_sources_sync = __esm({
|
|
835
850
|
"lib/server/config/sources-sync.js"() {
|
|
836
851
|
"use strict";
|
|
837
852
|
init_sources();
|
|
838
|
-
|
|
839
|
-
});
|
|
840
|
-
|
|
841
|
-
// lib/server/workspace/impl/root-resolution/index.js
|
|
842
|
-
var root_resolution_exports = {};
|
|
843
|
-
__export(root_resolution_exports, {
|
|
844
|
-
findProjectRoot: () => findProjectRoot,
|
|
845
|
-
makeResolveRoot: () => makeResolveRoot,
|
|
846
|
-
normalizedProjectRoot: () => normalizedProjectRoot
|
|
847
|
-
});
|
|
848
|
-
import { existsSync as existsSync3, realpathSync } from "node:fs";
|
|
849
|
-
import { dirname as dirname6, join as join6, resolve } from "node:path";
|
|
850
|
-
function dshHomePath() {
|
|
851
|
-
return resolve(dshHome());
|
|
852
|
-
}
|
|
853
|
-
async function findProjectRoot(cwd) {
|
|
854
|
-
const home = dshHomePath();
|
|
855
|
-
let current = resolve(cwd ?? process.cwd());
|
|
856
|
-
for (let depth = 0; depth < 16; depth += 1) {
|
|
857
|
-
const dotDsh = join6(current, ".dsh");
|
|
858
|
-
const hasProjectDsh = existsSync3(dotDsh) && resolve(dotDsh) !== home;
|
|
859
|
-
if (existsSync3(join6(current, ".git")) || hasProjectDsh || existsSync3(join6(current, ".mcp.json"))) {
|
|
860
|
-
return current;
|
|
861
|
-
}
|
|
862
|
-
const parent = dirname6(current);
|
|
863
|
-
if (parent === current)
|
|
864
|
-
break;
|
|
865
|
-
current = parent;
|
|
866
|
-
}
|
|
867
|
-
return resolve(cwd ?? process.cwd());
|
|
868
|
-
}
|
|
869
|
-
async function normalizedProjectRoot(cwd) {
|
|
870
|
-
if (cwd === void 0 || cwd === null || cwd === "")
|
|
871
|
-
return void 0;
|
|
872
|
-
const root = await findProjectRoot(cwd);
|
|
873
|
-
try {
|
|
874
|
-
return realpathSync(root);
|
|
875
|
-
} catch {
|
|
876
|
-
return root;
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
function makeResolveRoot(manager) {
|
|
880
|
-
return async (agent) => {
|
|
881
|
-
if (typeof agent !== "object" || agent === null)
|
|
882
|
-
return void 0;
|
|
883
|
-
const session = agent.session;
|
|
884
|
-
const cwd = session?.header?.cwd;
|
|
885
|
-
const root = await normalizedProjectRoot(typeof cwd === "string" ? cwd : void 0);
|
|
886
|
-
if (root !== void 0)
|
|
887
|
-
return root;
|
|
888
|
-
const globalServers = (await manager.projectServersFor(MIDDLEWARE_GLOBAL_ROOT) ?? []).filter((server) => server.enabled !== false);
|
|
889
|
-
if (globalServers.length > 0)
|
|
890
|
-
return MIDDLEWARE_GLOBAL_ROOT;
|
|
891
|
-
return void 0;
|
|
892
|
-
};
|
|
893
|
-
}
|
|
894
|
-
var init_root_resolution = __esm({
|
|
895
|
-
"lib/server/workspace/impl/root-resolution/index.js"() {
|
|
896
|
-
"use strict";
|
|
897
|
-
init_dsh_home();
|
|
898
|
-
init_interface2();
|
|
853
|
+
init_normalize();
|
|
899
854
|
}
|
|
900
855
|
});
|
|
901
856
|
|
|
@@ -1924,8 +1879,9 @@ var require_lib2 = __commonJS({
|
|
|
1924
1879
|
});
|
|
1925
1880
|
|
|
1926
1881
|
// lib/index.js
|
|
1882
|
+
init_sources_sync();
|
|
1927
1883
|
init_sources();
|
|
1928
|
-
import { dirname as
|
|
1884
|
+
import { dirname as dirname12 } from "node:path";
|
|
1929
1885
|
|
|
1930
1886
|
// lib/vendor/shared/settings-namespace.js
|
|
1931
1887
|
function isUnloading(ctx) {
|
|
@@ -2315,11 +2271,98 @@ var OrchestratorPorts = class {
|
|
|
2315
2271
|
var orchestratorPorts = new OrchestratorPorts();
|
|
2316
2272
|
|
|
2317
2273
|
// lib/server/connection/orchestrator/manager.js
|
|
2318
|
-
init_interface2();
|
|
2319
2274
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
2320
2275
|
import { mkdir as mkdir2, rename as rename2, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
|
|
2321
2276
|
import { existsSync as existsSync2 } from "node:fs";
|
|
2322
2277
|
import { dirname as dirname4 } from "node:path";
|
|
2278
|
+
|
|
2279
|
+
// lib/shared/placement-math.js
|
|
2280
|
+
var DEFAULT_Z_INDEX_BASE = 10;
|
|
2281
|
+
var Z_INDEX_BASE_MIN = 1;
|
|
2282
|
+
var Z_INDEX_BASE_MAX = 9e3;
|
|
2283
|
+
function clampZIndexBase(value, dflt) {
|
|
2284
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
2285
|
+
return dflt;
|
|
2286
|
+
return Math.min(Z_INDEX_BASE_MAX, Math.max(Z_INDEX_BASE_MIN, Math.round(value)));
|
|
2287
|
+
}
|
|
2288
|
+
function panelTopForAnchor(anchor, pillTop, pillBottom, panelHeight, gap) {
|
|
2289
|
+
return anchor === "bottom" ? Math.max(6, pillTop - panelHeight - gap) : Math.max(6, pillBottom + gap);
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
// lib/shared/status.js
|
|
2293
|
+
var SERVER_STATES = {
|
|
2294
|
+
connected: "connected",
|
|
2295
|
+
connecting: "connecting",
|
|
2296
|
+
reconnecting: "reconnecting",
|
|
2297
|
+
disabled: "disabled",
|
|
2298
|
+
stopped: "stopped",
|
|
2299
|
+
failed: "failed"
|
|
2300
|
+
};
|
|
2301
|
+
var EMPTY_STATUS_COUNTS = {
|
|
2302
|
+
connected: 0,
|
|
2303
|
+
connecting: 0,
|
|
2304
|
+
reconnecting: 0,
|
|
2305
|
+
disabled: 0,
|
|
2306
|
+
stopped: 0,
|
|
2307
|
+
failed: 0
|
|
2308
|
+
};
|
|
2309
|
+
|
|
2310
|
+
// lib/shared/frames.js
|
|
2311
|
+
var SSE_FRAMES = {
|
|
2312
|
+
summary: "summary",
|
|
2313
|
+
uiConfigChanged: "ui-config-changed",
|
|
2314
|
+
ping: "ping"
|
|
2315
|
+
};
|
|
2316
|
+
|
|
2317
|
+
// lib/shared/routes.js
|
|
2318
|
+
var ROUTES = {
|
|
2319
|
+
servers: "/api/dsh-mcp/servers",
|
|
2320
|
+
config: "/api/dsh-mcp/config",
|
|
2321
|
+
session: "/api/dsh-mcp/session",
|
|
2322
|
+
resume: "/api/dsh-mcp/resume",
|
|
2323
|
+
connect: "/api/dsh-mcp/servers/connect",
|
|
2324
|
+
disconnect: "/api/dsh-mcp/servers/disconnect",
|
|
2325
|
+
reconnect: "/api/dsh-mcp/servers/reconnect",
|
|
2326
|
+
importJson: "/api/dsh-mcp/import/json",
|
|
2327
|
+
sources: "/api/dsh-mcp/sources",
|
|
2328
|
+
sourceSave: "/api/dsh-mcp/sources/save",
|
|
2329
|
+
sourceActivate: "/api/dsh-mcp/sources/activate",
|
|
2330
|
+
events: "/api/dsh-mcp/events",
|
|
2331
|
+
health: "/api/dsh-mcp/health",
|
|
2332
|
+
toolDisable: "/api/dsh-mcp/tool-disable"
|
|
2333
|
+
};
|
|
2334
|
+
var ROUTE_FENCE = {
|
|
2335
|
+
servers: { guarded: ["GET", "POST", "PATCH", "DELETE"], loopbackExempt: [] },
|
|
2336
|
+
config: { guarded: ["POST"], loopbackExempt: ["GET"] },
|
|
2337
|
+
session: { guarded: ["POST"], loopbackExempt: [] },
|
|
2338
|
+
resume: { guarded: ["POST"], loopbackExempt: [] },
|
|
2339
|
+
connect: { guarded: ["POST"], loopbackExempt: [] },
|
|
2340
|
+
disconnect: { guarded: ["POST"], loopbackExempt: [] },
|
|
2341
|
+
reconnect: { guarded: ["POST"], loopbackExempt: [] },
|
|
2342
|
+
importJson: { guarded: ["POST"], loopbackExempt: [] },
|
|
2343
|
+
sources: { guarded: ["GET", "POST"], loopbackExempt: [] },
|
|
2344
|
+
sourceSave: { guarded: ["POST"], loopbackExempt: [] },
|
|
2345
|
+
sourceActivate: { guarded: ["POST"], loopbackExempt: [] },
|
|
2346
|
+
events: { guarded: ["GET"], loopbackExempt: [] },
|
|
2347
|
+
health: { guarded: ["GET"], loopbackExempt: [] },
|
|
2348
|
+
toolDisable: { guarded: ["PATCH"], loopbackExempt: [] }
|
|
2349
|
+
};
|
|
2350
|
+
|
|
2351
|
+
// lib/shared/constants.js
|
|
2352
|
+
var SCOPE_GLOBAL = "global";
|
|
2353
|
+
var SCOPE_PROJECT = "project";
|
|
2354
|
+
var MIDDLEWARE_GLOBAL_ROOT = "@global";
|
|
2355
|
+
var MCP_MANAGER_BUNDLE_PACKAGE = "dsh-mcp-manager";
|
|
2356
|
+
var MCP_MANAGER_ROW_ID = "dsh-mcp-manager";
|
|
2357
|
+
var MCP_MANAGER_SETTINGS_NAMESPACE = "dsh-mcp-manager";
|
|
2358
|
+
var MCP_MANAGER_IDENTITY = Object.freeze({
|
|
2359
|
+
bundlePackage: MCP_MANAGER_BUNDLE_PACKAGE,
|
|
2360
|
+
rowId: MCP_MANAGER_ROW_ID,
|
|
2361
|
+
settingsNamespace: MCP_MANAGER_SETTINGS_NAMESPACE,
|
|
2362
|
+
rowConfigKey: `${MCP_MANAGER_BUNDLE_PACKAGE}#${MCP_MANAGER_ROW_ID}`
|
|
2363
|
+
});
|
|
2364
|
+
|
|
2365
|
+
// lib/server/connection/orchestrator/manager.js
|
|
2323
2366
|
init_interface();
|
|
2324
2367
|
function isPlainRecord(value) {
|
|
2325
2368
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -2455,6 +2498,8 @@ function projectBaseFieldsForSummary(server) {
|
|
|
2455
2498
|
name: server.name,
|
|
2456
2499
|
transport: server.transport
|
|
2457
2500
|
};
|
|
2501
|
+
if (server.source !== void 0)
|
|
2502
|
+
projected.source = server.source;
|
|
2458
2503
|
if (server.enabled !== void 0)
|
|
2459
2504
|
projected.enabled = server.enabled;
|
|
2460
2505
|
if (server.toolCallTimeoutMs !== void 0)
|
|
@@ -2537,11 +2582,13 @@ function touchUnitInBackground(middleware, target, onFail) {
|
|
|
2537
2582
|
middleware.evictIfNeeded();
|
|
2538
2583
|
}, (error) => onFail(error));
|
|
2539
2584
|
}
|
|
2540
|
-
function releaseUndesiredConnections(middleware, desired) {
|
|
2585
|
+
function releaseUndesiredConnections(middleware, desired, projectRoot) {
|
|
2541
2586
|
if (middleware === void 0)
|
|
2542
2587
|
return false;
|
|
2543
2588
|
let released = false;
|
|
2544
2589
|
for (const unit of [...middleware.units.values()]) {
|
|
2590
|
+
if (unit.root !== MIDDLEWARE_GLOBAL_ROOT && unit.root !== projectRoot)
|
|
2591
|
+
continue;
|
|
2545
2592
|
for (const serverName of [...unit.connections.keys()]) {
|
|
2546
2593
|
const want = desired.get(`${unit.root}\0${serverName}`);
|
|
2547
2594
|
if (want !== void 0 && want.server.enabled !== false)
|
|
@@ -2561,7 +2608,7 @@ function touchRootFor(scope, projectRoot) {
|
|
|
2561
2608
|
return scope === SCOPE_PROJECT ? projectRoot : MIDDLEWARE_GLOBAL_ROOT;
|
|
2562
2609
|
}
|
|
2563
2610
|
function syncPool(middleware, desired, projectRoot, start) {
|
|
2564
|
-
let changed = releaseUndesiredConnections(middleware, desired);
|
|
2611
|
+
let changed = releaseUndesiredConnections(middleware, desired, projectRoot);
|
|
2565
2612
|
for (const want of desired.values()) {
|
|
2566
2613
|
if (want.server.enabled === false)
|
|
2567
2614
|
continue;
|
|
@@ -2828,7 +2875,10 @@ var McpManager = class {
|
|
|
2828
2875
|
getRedactionServers() {
|
|
2829
2876
|
const { configModel } = orchestratorPorts.get();
|
|
2830
2877
|
const servers = this.store.data.servers.map((server) => configModel.expandServerEnv(server));
|
|
2831
|
-
|
|
2878
|
+
const cachedStores = new Set(this.projectStores.values());
|
|
2879
|
+
if (this.projectStore !== void 0)
|
|
2880
|
+
cachedStores.add(this.projectStore);
|
|
2881
|
+
for (const store of cachedStores) {
|
|
2832
2882
|
for (const server of store.data.servers)
|
|
2833
2883
|
servers.push(configModel.expandServerEnv(server));
|
|
2834
2884
|
}
|
|
@@ -2876,7 +2926,10 @@ var McpManager = class {
|
|
|
2876
2926
|
*/
|
|
2877
2927
|
async projectServersFor(root) {
|
|
2878
2928
|
if (root === MIDDLEWARE_GLOBAL_ROOT) {
|
|
2879
|
-
const servers =
|
|
2929
|
+
const servers = this.store.data.servers.map((server) => ({
|
|
2930
|
+
...server,
|
|
2931
|
+
enabled: server.enabled !== false || [...this.disabledByRoot.values()].some((flags) => flags.has(`@global-enabled/${server.name}`))
|
|
2932
|
+
}));
|
|
2880
2933
|
for (const server of this.runtimeRegistry.values())
|
|
2881
2934
|
servers.push(server);
|
|
2882
2935
|
return servers;
|
|
@@ -2925,7 +2978,8 @@ var McpManager = class {
|
|
|
2925
2978
|
catalogCachePath: (root) => this.catalogCachePathFor(root),
|
|
2926
2979
|
isGlobalServer: (name2) => this.isGlobalServer(name2),
|
|
2927
2980
|
isRuntimeServer: (name2) => this.isRuntimeServer(name2),
|
|
2928
|
-
recordCatalogTools: (name2, tools) => this.recordCatalogTools(name2, tools)
|
|
2981
|
+
recordCatalogTools: (name2, tools) => this.recordCatalogTools(name2, tools),
|
|
2982
|
+
serverEnabledFor: (root, target, name2) => target !== MIDDLEWARE_GLOBAL_ROOT || this.globalEnabledFor(root, name2)
|
|
2929
2983
|
});
|
|
2930
2984
|
try {
|
|
2931
2985
|
this.disabledByRoot = await configStore.loadUserState(this.userStatePath);
|
|
@@ -2942,6 +2996,48 @@ var McpManager = class {
|
|
|
2942
2996
|
}
|
|
2943
2997
|
/** userDisabled 映射(root → Set<server>),中间层单元创建时合并。 */
|
|
2944
2998
|
disabledByRoot = /* @__PURE__ */ new Map();
|
|
2999
|
+
/** Workspace-local switches for shared global transports (stored with the existing root state). */
|
|
3000
|
+
globalEnabledFor(root, name2) {
|
|
3001
|
+
const flags = this.middleware?.units.get(root)?.userDisabled ?? this.disabledByRoot.get(root);
|
|
3002
|
+
if (flags?.has(`@global-enabled/${name2}`))
|
|
3003
|
+
return true;
|
|
3004
|
+
if (flags?.has(`@global/${name2}`))
|
|
3005
|
+
return false;
|
|
3006
|
+
return (this.runtimeRegistry.get(name2) ?? this.store.find(name2))?.enabled !== false;
|
|
3007
|
+
}
|
|
3008
|
+
async setGlobalEnabled(name2, enabled) {
|
|
3009
|
+
const server = this.store.find(name2) ?? this.runtimeRegistry.get(name2);
|
|
3010
|
+
if (server === void 0)
|
|
3011
|
+
throw new Error(`server "${name2}" not found in global scope`);
|
|
3012
|
+
const root = this.projectRoot ?? MIDDLEWARE_GLOBAL_ROOT;
|
|
3013
|
+
const mw = this.middleware;
|
|
3014
|
+
const flags = mw?.units.get(root)?.userDisabled ?? new Set(this.disabledByRoot.get(root));
|
|
3015
|
+
flags.delete(`@global/${name2}`);
|
|
3016
|
+
flags.delete(`@global-enabled/${name2}`);
|
|
3017
|
+
flags.add(`${enabled ? "@global-enabled" : "@global"}/${name2}`);
|
|
3018
|
+
this.disabledByRoot.set(root, flags);
|
|
3019
|
+
if (mw !== void 0) {
|
|
3020
|
+
if (enabled)
|
|
3021
|
+
mw.units.get(MIDDLEWARE_GLOBAL_ROOT)?.userDisabled.delete(name2);
|
|
3022
|
+
const units = new Map(mw.units);
|
|
3023
|
+
for (const [savedRoot, savedFlags] of this.disabledByRoot) {
|
|
3024
|
+
if (!units.has(savedRoot))
|
|
3025
|
+
units.set(savedRoot, {
|
|
3026
|
+
root: savedRoot,
|
|
3027
|
+
userDisabled: savedFlags,
|
|
3028
|
+
connections: /* @__PURE__ */ new Map(),
|
|
3029
|
+
inFlight: /* @__PURE__ */ new Map(),
|
|
3030
|
+
lastTouchedAt: Date.now()
|
|
3031
|
+
});
|
|
3032
|
+
}
|
|
3033
|
+
units.set(root, { root, userDisabled: flags, connections: /* @__PURE__ */ new Map(), inFlight: /* @__PURE__ */ new Map(), lastTouchedAt: Date.now() });
|
|
3034
|
+
await this.saveUserState(units);
|
|
3035
|
+
if (enabled)
|
|
3036
|
+
this.touchGlobalUnit(name2);
|
|
3037
|
+
}
|
|
3038
|
+
this.emitStatus();
|
|
3039
|
+
return { ...server, enabled };
|
|
3040
|
+
}
|
|
2945
3041
|
/** 工具级禁用(root → server → Set<tool>);root=@global 跨工作空间共享。 */
|
|
2946
3042
|
disabledTools = /* @__PURE__ */ new Map();
|
|
2947
3043
|
/**
|
|
@@ -2977,24 +3073,31 @@ var McpManager = class {
|
|
|
2977
3073
|
async projectStoreFor(root) {
|
|
2978
3074
|
if (typeof root !== "string" || root === "")
|
|
2979
3075
|
return void 0;
|
|
2980
|
-
const
|
|
2981
|
-
let store = this.projectStores.get(root);
|
|
3076
|
+
const store = this.projectStores.get(root);
|
|
2982
3077
|
if (store === void 0) {
|
|
3078
|
+
const pending = this.projectStoreLoads.get(root);
|
|
3079
|
+
if (pending !== void 0)
|
|
3080
|
+
return pending;
|
|
3081
|
+
const load = this.loadProjectStore(root);
|
|
3082
|
+
this.projectStoreLoads.set(root, load);
|
|
3083
|
+
try {
|
|
3084
|
+
return await load;
|
|
3085
|
+
} finally {
|
|
3086
|
+
this.projectStoreLoads.delete(root);
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
return store;
|
|
3090
|
+
}
|
|
3091
|
+
async loadProjectStore(root) {
|
|
3092
|
+
const { configStore, upgrade } = orchestratorPorts.get();
|
|
3093
|
+
let store;
|
|
3094
|
+
{
|
|
2983
3095
|
store = await upgrade.withSettledProjectConfig(root, this.logger, async (settledPath) => {
|
|
2984
3096
|
const fresh = new configStore.McpStore(settledPath);
|
|
2985
3097
|
await fresh.load();
|
|
2986
3098
|
return fresh;
|
|
2987
3099
|
});
|
|
2988
3100
|
this.projectStores.set(root, store);
|
|
2989
|
-
} else {
|
|
2990
|
-
const cached = store;
|
|
2991
|
-
try {
|
|
2992
|
-
await upgrade.withSettledProjectConfig(root, this.logger, async () => {
|
|
2993
|
-
await cached.reloadIfChanged();
|
|
2994
|
-
});
|
|
2995
|
-
} catch (error) {
|
|
2996
|
-
this.logger.warn(`dsh-mcp-manager: reload project config failed: ${this.redactError(error)}`);
|
|
2997
|
-
}
|
|
2998
3101
|
}
|
|
2999
3102
|
return store;
|
|
3000
3103
|
}
|
|
@@ -3020,10 +3123,16 @@ var McpManager = class {
|
|
|
3020
3123
|
*/
|
|
3021
3124
|
async catalogServersFor(cwd) {
|
|
3022
3125
|
const servers = /* @__PURE__ */ new Map();
|
|
3023
|
-
|
|
3126
|
+
const root = await this.workspaceRoot(cwd);
|
|
3127
|
+
collectCatalogServers(servers, this.store.data.servers.map((server) => serverEntry({
|
|
3128
|
+
...server,
|
|
3129
|
+
enabled: this.globalEnabledFor(root ?? MIDDLEWARE_GLOBAL_ROOT, server.name)
|
|
3130
|
+
})), SCOPE_GLOBAL);
|
|
3024
3131
|
collectCatalogServers(servers, this.runtimeRegistry, SCOPE_GLOBAL);
|
|
3025
|
-
const
|
|
3026
|
-
|
|
3132
|
+
for (const [name2, entry] of servers) {
|
|
3133
|
+
if (entry.scope === SCOPE_GLOBAL && !this.globalEnabledFor(root ?? MIDDLEWARE_GLOBAL_ROOT, name2))
|
|
3134
|
+
servers.delete(name2);
|
|
3135
|
+
}
|
|
3027
3136
|
if (root === void 0)
|
|
3028
3137
|
return servers;
|
|
3029
3138
|
const store = await this.projectStoreFor(root);
|
|
@@ -3043,34 +3152,63 @@ var McpManager = class {
|
|
|
3043
3152
|
async catalogViewFor(cwd, servers) {
|
|
3044
3153
|
return this.catalogViewResolver(cwd, servers);
|
|
3045
3154
|
}
|
|
3046
|
-
/**
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3155
|
+
/** Cached workspace resolution and preparation never wait for a remote MCP server. */
|
|
3156
|
+
workspaceRoots = /* @__PURE__ */ new Map();
|
|
3157
|
+
selectionVersion = 0;
|
|
3158
|
+
touchedRoots = /* @__PURE__ */ new Set();
|
|
3159
|
+
projectStoreLoads = /* @__PURE__ */ new Map();
|
|
3160
|
+
sourceSnapshot;
|
|
3161
|
+
sourcesChanged;
|
|
3162
|
+
prepareWorkspace;
|
|
3163
|
+
workspaceRoot(cwd) {
|
|
3164
|
+
const key = cwd ?? "";
|
|
3165
|
+
let root = this.workspaceRoots.get(key);
|
|
3166
|
+
if (root === void 0) {
|
|
3167
|
+
root = projectRootForCwd(cwd, orchestratorPorts.get().workspace.findProjectRoot);
|
|
3168
|
+
this.workspaceRoots.set(key, root);
|
|
3169
|
+
void root.catch(() => this.workspaceRoots.delete(key));
|
|
3170
|
+
}
|
|
3171
|
+
return root;
|
|
3172
|
+
}
|
|
3173
|
+
/** A request owns its workspace view; asynchronous requests cannot change another request's root. */
|
|
3174
|
+
async forWorkspace(cwd) {
|
|
3175
|
+
const root = await this.workspaceRoot(cwd);
|
|
3176
|
+
const view = Object.create(this);
|
|
3177
|
+
view.projectRoot = root;
|
|
3178
|
+
view.projectStore = root === void 0 ? void 0 : await this.projectStoreFor(root);
|
|
3179
|
+
view.setSession = async () => {
|
|
3180
|
+
};
|
|
3181
|
+
view.emitStatus = this.emitStatus.bind(this);
|
|
3182
|
+
return view;
|
|
3183
|
+
}
|
|
3184
|
+
/** Selection changes pointers only. Source sync/connection checks belong to background preparation. */
|
|
3052
3185
|
async setSession(cwd) {
|
|
3053
|
-
const
|
|
3054
|
-
const root = await
|
|
3055
|
-
if (
|
|
3186
|
+
const version = ++this.selectionVersion;
|
|
3187
|
+
const root = await this.workspaceRoot(cwd);
|
|
3188
|
+
if (version !== this.selectionVersion)
|
|
3056
3189
|
return;
|
|
3057
|
-
if (root === void 0
|
|
3190
|
+
if (root === this.projectRoot && (root === void 0 || this.projectStore !== void 0))
|
|
3191
|
+
return;
|
|
3192
|
+
const store = root === void 0 ? void 0 : await this.projectStoreFor(root);
|
|
3193
|
+
if (version !== this.selectionVersion)
|
|
3058
3194
|
return;
|
|
3059
3195
|
this.projectRoot = root;
|
|
3060
|
-
this.projectStore =
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
}
|
|
3064
|
-
await reloadStoreQuietly(this.store, (error) => {
|
|
3065
|
-
this.logger.warn(`dsh-mcp-manager: reload global config failed: ${this.redactError(error)}`);
|
|
3066
|
-
});
|
|
3067
|
-
if (this.middleware !== void 0) {
|
|
3196
|
+
this.projectStore = store;
|
|
3197
|
+
this.emitStatus();
|
|
3198
|
+
if (this.prepareWorkspace === void 0 && this.middleware !== void 0) {
|
|
3068
3199
|
const target = root ?? MIDDLEWARE_GLOBAL_ROOT;
|
|
3069
|
-
|
|
3070
|
-
this.
|
|
3071
|
-
|
|
3200
|
+
if (!this.touchedRoots.has(target)) {
|
|
3201
|
+
this.touchedRoots.add(target);
|
|
3202
|
+
touchUnitInBackground(this.middleware, target, (error) => this.logger.warn(`dsh-mcp-manager: setSession touch unit(${target}) failed: ${this.redactError(error)}`));
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
if (this.prepareWorkspace !== void 0) {
|
|
3206
|
+
setTimeout(() => {
|
|
3207
|
+
void this.prepareWorkspace?.(cwd).catch((error) => {
|
|
3208
|
+
this.logger.warn(`dsh-mcp-manager: workspace preparation failed: ${this.redactError(error)}`);
|
|
3209
|
+
});
|
|
3210
|
+
}, 0).unref?.();
|
|
3072
3211
|
}
|
|
3073
|
-
this.emitStatus();
|
|
3074
3212
|
}
|
|
3075
3213
|
/**
|
|
3076
3214
|
* 切回前台可用性恢复:对本次请求工作空间连接的非强制恢复(对齐 SSE forceReconnect 的
|
|
@@ -3128,10 +3266,25 @@ var McpManager = class {
|
|
|
3128
3266
|
let configChanged = await reloadStoreQuietly(this.store, (error) => {
|
|
3129
3267
|
this.logger.warn(`dsh-mcp-manager: reload global config failed: ${this.redactError(error)}`);
|
|
3130
3268
|
});
|
|
3131
|
-
|
|
3132
|
-
|
|
3269
|
+
const cachedStores = new Set(this.projectStores.values());
|
|
3270
|
+
if (this.projectStore !== void 0)
|
|
3271
|
+
cachedStores.add(this.projectStore);
|
|
3272
|
+
for (const store of cachedStores) {
|
|
3273
|
+
const cachedRoot = [...this.projectStores].find(([, candidate]) => candidate === store)?.[0];
|
|
3274
|
+
if (cachedRoot !== void 0)
|
|
3275
|
+
await orchestratorPorts.get().upgrade.withSettledProjectConfig(cachedRoot, this.logger, async () => {
|
|
3276
|
+
});
|
|
3277
|
+
const changed = await reloadStoreQuietly(store, (error) => {
|
|
3133
3278
|
this.logger.warn(`dsh-mcp-manager: reload project config failed: ${this.redactError(error)}`);
|
|
3134
|
-
})
|
|
3279
|
+
});
|
|
3280
|
+
if (changed) {
|
|
3281
|
+
const root = [...this.projectStores].find(([, candidate]) => candidate === store)?.[0];
|
|
3282
|
+
if (root !== void 0) {
|
|
3283
|
+
const view = await this.forWorkspace(root);
|
|
3284
|
+
view.reconcileServers();
|
|
3285
|
+
}
|
|
3286
|
+
configChanged = true;
|
|
3287
|
+
}
|
|
3135
3288
|
}
|
|
3136
3289
|
if (!configChanged)
|
|
3137
3290
|
return;
|
|
@@ -3154,13 +3307,21 @@ var McpManager = class {
|
|
|
3154
3307
|
return false;
|
|
3155
3308
|
this.reconcileBusy = true;
|
|
3156
3309
|
try {
|
|
3157
|
-
return syncPool(this.middleware, buildDesiredServers(this.
|
|
3310
|
+
return syncPool(this.middleware, buildDesiredServers(this.transportStore(), this.projectStore, this.runtimeRegistry, (scope) => this.poolRootFor(scope)), this.projectRoot, (name2, scope) => this.start(name2, scope));
|
|
3158
3311
|
} finally {
|
|
3159
3312
|
this.reconcileBusy = false;
|
|
3160
3313
|
}
|
|
3161
3314
|
}
|
|
3315
|
+
transportStore() {
|
|
3316
|
+
const view = Object.create(this.store);
|
|
3317
|
+
view.data = { ...this.store.data, servers: this.store.data.servers.map((server) => ({
|
|
3318
|
+
...server,
|
|
3319
|
+
enabled: server.enabled !== false || [...this.disabledByRoot.values()].some((flags) => flags.has(`@global-enabled/${server.name}`))
|
|
3320
|
+
})) };
|
|
3321
|
+
return view;
|
|
3322
|
+
}
|
|
3162
3323
|
async startAll() {
|
|
3163
|
-
for (const server of this.
|
|
3324
|
+
for (const server of this.transportStore().data.servers) {
|
|
3164
3325
|
if (server.enabled !== false)
|
|
3165
3326
|
this.start(server.name, SCOPE_GLOBAL);
|
|
3166
3327
|
}
|
|
@@ -3239,16 +3400,17 @@ var McpManager = class {
|
|
|
3239
3400
|
});
|
|
3240
3401
|
}
|
|
3241
3402
|
/**
|
|
3242
|
-
*
|
|
3243
|
-
* 不写 userDisabled
|
|
3244
|
-
* 处理项目单元,@global 池连接与目录残留导致「已删服务器仍可调用」。
|
|
3403
|
+
* 只拆除目标 (root, server) 的连接与目录,不影响其他工作区的同名服务器。
|
|
3404
|
+
* 不写 userDisabled——配置变更与用户主动断开的语义不同。
|
|
3245
3405
|
*/
|
|
3246
|
-
dropMiddlewareConnection(name2) {
|
|
3406
|
+
dropMiddlewareConnection(name2, root) {
|
|
3247
3407
|
const mw = this.middleware;
|
|
3248
3408
|
if (mw === void 0)
|
|
3249
3409
|
return;
|
|
3250
3410
|
let dropped = false;
|
|
3251
3411
|
for (const unit of mw.units.values()) {
|
|
3412
|
+
if (unit.root !== root)
|
|
3413
|
+
continue;
|
|
3252
3414
|
if (mw.releaseConnection(unit.root, name2))
|
|
3253
3415
|
dropped = true;
|
|
3254
3416
|
const { catalogDirectory: catalogDirectory2 } = orchestratorPorts.get().catalog;
|
|
@@ -3262,7 +3424,7 @@ var McpManager = class {
|
|
|
3262
3424
|
dropped = true;
|
|
3263
3425
|
}
|
|
3264
3426
|
}
|
|
3265
|
-
mw.abandonInFlight(name2);
|
|
3427
|
+
mw.abandonInFlight(name2, root);
|
|
3266
3428
|
if (dropped)
|
|
3267
3429
|
this.emitStatus();
|
|
3268
3430
|
}
|
|
@@ -3308,7 +3470,7 @@ var McpManager = class {
|
|
|
3308
3470
|
const run = this.registerQueue.then(async () => {
|
|
3309
3471
|
if (this.runtimeRegistry.has(name2)) {
|
|
3310
3472
|
this.runtimeRegistry.delete(name2);
|
|
3311
|
-
this.dropMiddlewareConnection(name2);
|
|
3473
|
+
this.dropMiddlewareConnection(name2, MIDDLEWARE_GLOBAL_ROOT);
|
|
3312
3474
|
this.reconcileServers();
|
|
3313
3475
|
}
|
|
3314
3476
|
});
|
|
@@ -3334,7 +3496,11 @@ var McpManager = class {
|
|
|
3334
3496
|
}
|
|
3335
3497
|
async update(name2, patch, scope = SCOPE_GLOBAL) {
|
|
3336
3498
|
const { configModel } = orchestratorPorts.get();
|
|
3499
|
+
if (scope !== SCOPE_PROJECT && Object.keys(patch).length === 1 && typeof patch.enabled === "boolean") {
|
|
3500
|
+
return this.setGlobalEnabled(name2, patch.enabled);
|
|
3501
|
+
}
|
|
3337
3502
|
const store = scope === SCOPE_PROJECT ? await this.projectStoreOrThrow() : this.store;
|
|
3503
|
+
const root = this.poolRootFor(scope);
|
|
3338
3504
|
const existing = store.find(name2);
|
|
3339
3505
|
if (existing === void 0)
|
|
3340
3506
|
throw new Error(`server "${name2}" not found in ${scope} scope`);
|
|
@@ -3346,15 +3512,16 @@ var McpManager = class {
|
|
|
3346
3512
|
configModel.assertEnvPolicy(merged.env, merged.headers, void 0, merged.url, merged.args);
|
|
3347
3513
|
store.upsert(merged);
|
|
3348
3514
|
await store.save();
|
|
3349
|
-
this.dropMiddlewareConnection(name2);
|
|
3350
|
-
if (merged.enabled !== false) {
|
|
3515
|
+
this.dropMiddlewareConnection(name2, root);
|
|
3516
|
+
if ((scope === SCOPE_PROJECT ? merged.enabled : this.transportStore().find(name2)?.enabled) !== false) {
|
|
3351
3517
|
this.start(name2, scope);
|
|
3352
3518
|
}
|
|
3353
3519
|
return merged;
|
|
3354
3520
|
}
|
|
3355
3521
|
async remove(name2, scope = SCOPE_GLOBAL) {
|
|
3356
3522
|
const store = scope === SCOPE_PROJECT ? await this.projectStoreOrThrow() : this.store;
|
|
3357
|
-
this.
|
|
3523
|
+
const root = this.poolRootFor(scope);
|
|
3524
|
+
this.dropMiddlewareConnection(name2, root);
|
|
3358
3525
|
store.remove(name2);
|
|
3359
3526
|
await store.save();
|
|
3360
3527
|
}
|
|
@@ -3413,9 +3580,38 @@ var McpManager = class {
|
|
|
3413
3580
|
targetUnit.userDisabled.add(name2);
|
|
3414
3581
|
await this.saveUserState(mw.units);
|
|
3415
3582
|
mw.releaseConnection(targetUnit.root, name2);
|
|
3416
|
-
mw.abandonInFlight(name2);
|
|
3583
|
+
mw.abandonInFlight(name2, targetUnit.root);
|
|
3417
3584
|
this.emitStatus();
|
|
3418
3585
|
}
|
|
3586
|
+
/**
|
|
3587
|
+
* 孤儿禁用痕清扫:userDisabled 里挂着但对应 scope 的生效表已无此名的记录
|
|
3588
|
+
* 一律移除。早期互斥清理经 disconnect 落下的污染痕(同名切走后残留)会让
|
|
3589
|
+
* 之后切回/重启时的 start 静默短路(userDisabled 命中不连),表现为「经常
|
|
3590
|
+
* 连接不全」;生效表已是唯一事实,孤儿痕无语义。
|
|
3591
|
+
*/
|
|
3592
|
+
async sweepOrphanUserDisabled(validGlobal, validProject) {
|
|
3593
|
+
const mw = this.middleware;
|
|
3594
|
+
if (mw === void 0)
|
|
3595
|
+
return;
|
|
3596
|
+
let changed = false;
|
|
3597
|
+
for (const unit of mw.units.values()) {
|
|
3598
|
+
if (unit.root !== MIDDLEWARE_GLOBAL_ROOT && unit.root !== this.projectRoot)
|
|
3599
|
+
continue;
|
|
3600
|
+
const valid = unit.root === MIDDLEWARE_GLOBAL_ROOT ? validGlobal : validProject;
|
|
3601
|
+
if (valid.size === 0 && unit.userDisabled.size === 0)
|
|
3602
|
+
continue;
|
|
3603
|
+
for (const name2 of [...unit.userDisabled]) {
|
|
3604
|
+
if (name2.startsWith("@global/") || name2.startsWith("@global-enabled/"))
|
|
3605
|
+
continue;
|
|
3606
|
+
if (!valid.has(name2)) {
|
|
3607
|
+
unit.userDisabled.delete(name2);
|
|
3608
|
+
changed = true;
|
|
3609
|
+
}
|
|
3610
|
+
}
|
|
3611
|
+
}
|
|
3612
|
+
if (changed)
|
|
3613
|
+
await this.saveUserState(mw.units);
|
|
3614
|
+
}
|
|
3419
3615
|
async reconnect(name2, scope = SCOPE_GLOBAL) {
|
|
3420
3616
|
await this.disconnect(name2, scope);
|
|
3421
3617
|
await this.connect(name2, scope);
|
|
@@ -3457,7 +3653,13 @@ var McpManager = class {
|
|
|
3457
3653
|
}
|
|
3458
3654
|
summarize(server, scope) {
|
|
3459
3655
|
const { catalog: catalogPort, pipeline } = orchestratorPorts.get();
|
|
3656
|
+
if (scope !== SCOPE_PROJECT) {
|
|
3657
|
+
server = { ...server, enabled: this.globalEnabledFor(this.projectRoot ?? MIDDLEWARE_GLOBAL_ROOT, server.name) };
|
|
3658
|
+
}
|
|
3460
3659
|
const base = projectServerForSummary(server, pipeline.maskSecretArgsForDisplay);
|
|
3660
|
+
const workspaceFlags = this.middleware?.units.get(this.projectRoot ?? MIDDLEWARE_GLOBAL_ROOT)?.userDisabled ?? this.disabledByRoot.get(this.projectRoot ?? MIDDLEWARE_GLOBAL_ROOT);
|
|
3661
|
+
if (scope !== SCOPE_PROJECT && workspaceFlags?.has(`@global/${server.name}`))
|
|
3662
|
+
return summarizeAbsent(base, scope, server);
|
|
3461
3663
|
const unit = this.middlewareUnitFor(server.name, scope);
|
|
3462
3664
|
const entry = unit?.connections.get(server.name);
|
|
3463
3665
|
if (unit === void 0 || entry === void 0)
|
|
@@ -3665,17 +3867,63 @@ function createSseHub(options) {
|
|
|
3665
3867
|
};
|
|
3666
3868
|
}
|
|
3667
3869
|
|
|
3668
|
-
// lib/server/api/routes.js
|
|
3669
|
-
init_interface2();
|
|
3670
|
-
|
|
3671
3870
|
// lib/server/api/routes-controllers.js
|
|
3672
3871
|
init_sources();
|
|
3673
3872
|
init_sources_sync();
|
|
3674
|
-
init_root_resolution();
|
|
3675
|
-
init_interface();
|
|
3676
|
-
init_interface2();
|
|
3677
3873
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
3678
3874
|
|
|
3875
|
+
// lib/server/workspace/impl/root-resolution/index.js
|
|
3876
|
+
init_dsh_home();
|
|
3877
|
+
import { existsSync as existsSync3, realpathSync } from "node:fs";
|
|
3878
|
+
import { dirname as dirname5, join as join5, resolve } from "node:path";
|
|
3879
|
+
function dshHomePath() {
|
|
3880
|
+
return resolve(dshHome());
|
|
3881
|
+
}
|
|
3882
|
+
async function findProjectRoot(cwd) {
|
|
3883
|
+
const home = dshHomePath();
|
|
3884
|
+
let current = resolve(cwd ?? process.cwd());
|
|
3885
|
+
for (let depth = 0; depth < 16; depth += 1) {
|
|
3886
|
+
const dotDsh = join5(current, ".dsh");
|
|
3887
|
+
const hasProjectDsh = existsSync3(dotDsh) && resolve(dotDsh) !== home;
|
|
3888
|
+
if (existsSync3(join5(current, ".git")) || hasProjectDsh || existsSync3(join5(current, ".mcp.json"))) {
|
|
3889
|
+
return current;
|
|
3890
|
+
}
|
|
3891
|
+
const parent = dirname5(current);
|
|
3892
|
+
if (parent === current)
|
|
3893
|
+
break;
|
|
3894
|
+
current = parent;
|
|
3895
|
+
}
|
|
3896
|
+
return resolve(cwd ?? process.cwd());
|
|
3897
|
+
}
|
|
3898
|
+
async function normalizedProjectRoot(cwd) {
|
|
3899
|
+
if (cwd === void 0 || cwd === null || cwd === "")
|
|
3900
|
+
return void 0;
|
|
3901
|
+
const root = await findProjectRoot(cwd);
|
|
3902
|
+
try {
|
|
3903
|
+
return realpathSync(root);
|
|
3904
|
+
} catch {
|
|
3905
|
+
return root;
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
function makeResolveRoot(manager) {
|
|
3909
|
+
return async (agent) => {
|
|
3910
|
+
if (typeof agent !== "object" || agent === null)
|
|
3911
|
+
return void 0;
|
|
3912
|
+
const session = agent.session;
|
|
3913
|
+
const cwd = session?.header?.cwd;
|
|
3914
|
+
const root = await normalizedProjectRoot(typeof cwd === "string" ? cwd : void 0);
|
|
3915
|
+
if (root !== void 0)
|
|
3916
|
+
return root;
|
|
3917
|
+
const globalServers = (await manager.projectServersFor(MIDDLEWARE_GLOBAL_ROOT) ?? []).filter((server) => server.enabled !== false);
|
|
3918
|
+
if (globalServers.length > 0)
|
|
3919
|
+
return MIDDLEWARE_GLOBAL_ROOT;
|
|
3920
|
+
return void 0;
|
|
3921
|
+
};
|
|
3922
|
+
}
|
|
3923
|
+
|
|
3924
|
+
// lib/server/api/routes-controllers.js
|
|
3925
|
+
init_interface();
|
|
3926
|
+
|
|
3679
3927
|
// lib/server/api/routes-helpers.js
|
|
3680
3928
|
function queryParam(url, name2) {
|
|
3681
3929
|
const value = url.searchParams.get(name2);
|
|
@@ -3809,13 +4057,13 @@ function buildServersRoute(manager, helpers) {
|
|
|
3809
4057
|
const base = manager.summary();
|
|
3810
4058
|
const runtimeServers = base.servers ?? [];
|
|
3811
4059
|
try {
|
|
3812
|
-
const
|
|
3813
|
-
const
|
|
3814
|
-
const parsedSources = SOURCE_IDS.map((sid) => {
|
|
4060
|
+
const projectRoot = typeof base.projectRoot === "string" ? base.projectRoot : void 0;
|
|
4061
|
+
const snapshot = manager.sourceSnapshot?.();
|
|
4062
|
+
const parsedSources = manager.sourceSnapshot !== void 0 ? snapshot?.sources ?? [] : SOURCE_IDS.map((sid) => {
|
|
3815
4063
|
const p2 = sourcePath(sid, projectRoot);
|
|
3816
4064
|
return parseSourceEntry(sid, p2, p2 === null ? null : readSourceFile(p2));
|
|
3817
4065
|
});
|
|
3818
|
-
const prefs = await loadSourcePrefs();
|
|
4066
|
+
const prefs = snapshot?.prefs ?? (manager.sourceSnapshot !== void 0 ? /* @__PURE__ */ new Map() : await loadSourcePrefs());
|
|
3819
4067
|
const eff = resolveEffective(parsedSources, prefs);
|
|
3820
4068
|
const declared = [];
|
|
3821
4069
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3827,17 +4075,17 @@ function buildServersRoute(manager, helpers) {
|
|
|
3827
4075
|
seen.add(key);
|
|
3828
4076
|
const winner = eff.get(server.name);
|
|
3829
4077
|
const isActive = winner !== void 0 && winner.source === entry.id;
|
|
4078
|
+
const scope = sourceScope(entry.id);
|
|
4079
|
+
const runtime = runtimeServers.find((r) => r.name === server.name && r.scope === scope && (r.source === entry.id || isActive && r.source === void 0));
|
|
4080
|
+
if (runtime !== void 0)
|
|
4081
|
+
runtimeServers.splice(runtimeServers.indexOf(runtime), 1);
|
|
4082
|
+
const safe = manager.summarize?.(server, scope) ?? server;
|
|
3830
4083
|
if (isActive) {
|
|
3831
|
-
|
|
3832
|
-
declared.push({ ...server, source: entry.id, ...runtime ?? { status: "stopped", tools: [] } });
|
|
3833
|
-
if (runtime !== void 0) {
|
|
3834
|
-
const idx = runtimeServers.indexOf(runtime);
|
|
3835
|
-
if (idx >= 0)
|
|
3836
|
-
runtimeServers.splice(idx, 1);
|
|
3837
|
-
}
|
|
4084
|
+
declared.push({ ...safe, ...runtime ?? { status: "stopped", tools: [] }, scope, source: entry.id });
|
|
3838
4085
|
} else {
|
|
3839
4086
|
declared.push({
|
|
3840
|
-
...
|
|
4087
|
+
...safe,
|
|
4088
|
+
scope,
|
|
3841
4089
|
source: entry.id,
|
|
3842
4090
|
enabled: false,
|
|
3843
4091
|
status: "stopped",
|
|
@@ -3848,6 +4096,14 @@ function buildServersRoute(manager, helpers) {
|
|
|
3848
4096
|
}
|
|
3849
4097
|
}
|
|
3850
4098
|
base.servers = [...declared, ...runtimeServers];
|
|
4099
|
+
const counts = {};
|
|
4100
|
+
for (const server of base.servers) {
|
|
4101
|
+
if (server.shadowedBy !== void 0)
|
|
4102
|
+
continue;
|
|
4103
|
+
const status = String(server.status ?? "stopped");
|
|
4104
|
+
counts[status] = (counts[status] ?? 0) + 1;
|
|
4105
|
+
}
|
|
4106
|
+
base.counts = counts;
|
|
3851
4107
|
} catch {
|
|
3852
4108
|
}
|
|
3853
4109
|
writeJson(res, 200, base);
|
|
@@ -4196,7 +4452,15 @@ function buildSourceSaveRoute(manager, helpers) {
|
|
|
4196
4452
|
if (typeof rec.cwd === "string" && rec.cwd !== "")
|
|
4197
4453
|
await manager.setSession(rec.cwd);
|
|
4198
4454
|
const prefs = await loadSourcePrefs();
|
|
4199
|
-
const
|
|
4455
|
+
const scoped = manager.forWorkspace !== void 0 ? await manager.forWorkspace(cwd ?? manager.projectRoot) : manager;
|
|
4456
|
+
const globalSummary = sourceScope(id) === "global" ? await syncSourcesIntoManager(scoped, void 0, prefs) : void 0;
|
|
4457
|
+
const summary = projectRoot === void 0 ? globalSummary ?? await syncSourcesIntoManager(scoped, void 0, prefs) : await syncSourcesIntoManager(scoped, projectRoot, prefs);
|
|
4458
|
+
if (projectRoot !== void 0 && globalSummary !== void 0) {
|
|
4459
|
+
summary.effective.unshift(...globalSummary.effective);
|
|
4460
|
+
summary.removed.unshift(...globalSummary.removed);
|
|
4461
|
+
summary.warnings.unshift(...globalSummary.warnings);
|
|
4462
|
+
}
|
|
4463
|
+
await manager.sourcesChanged?.();
|
|
4200
4464
|
writeJson(res, 200, { ok: true, path, ...summary });
|
|
4201
4465
|
}
|
|
4202
4466
|
};
|
|
@@ -4213,8 +4477,8 @@ function buildSourceActivateRoute(manager, helpers) {
|
|
|
4213
4477
|
try {
|
|
4214
4478
|
const { appendFileSync } = await import("node:fs");
|
|
4215
4479
|
const { homedir: homedir3 } = await import("node:os");
|
|
4216
|
-
const { join:
|
|
4217
|
-
const p =
|
|
4480
|
+
const { join: join8 } = await import("node:path");
|
|
4481
|
+
const p = join8(homedir3(), ".dsh", "dsh-mcp-manager", "sync-debug.log");
|
|
4218
4482
|
appendFileSync(p, `[${(/* @__PURE__ */ new Date()).toISOString()}] activate ${line}
|
|
4219
4483
|
`);
|
|
4220
4484
|
} catch {
|
|
@@ -4252,9 +4516,17 @@ function buildSourceActivateRoute(manager, helpers) {
|
|
|
4252
4516
|
const persisted = saveSourcePrefs(prefs);
|
|
4253
4517
|
if (typeof rec.cwd === "string" && rec.cwd !== "")
|
|
4254
4518
|
await manager.setSession(rec.cwd);
|
|
4255
|
-
const
|
|
4519
|
+
const scoped = manager.forWorkspace !== void 0 ? await manager.forWorkspace(cwd ?? manager.projectRoot) : manager;
|
|
4520
|
+
const globalSummary = sourceScope(id) === "global" ? await syncSourcesIntoManager(scoped, void 0, prefs) : void 0;
|
|
4521
|
+
const summary = projectRoot === void 0 ? globalSummary ?? await syncSourcesIntoManager(scoped, void 0, prefs) : await syncSourcesIntoManager(scoped, projectRoot, prefs);
|
|
4522
|
+
if (projectRoot !== void 0 && globalSummary !== void 0) {
|
|
4523
|
+
summary.effective.unshift(...globalSummary.effective);
|
|
4524
|
+
summary.removed.unshift(...globalSummary.removed);
|
|
4525
|
+
summary.warnings.unshift(...globalSummary.warnings);
|
|
4526
|
+
}
|
|
4527
|
+
await manager.sourcesChanged?.();
|
|
4256
4528
|
try {
|
|
4257
|
-
await
|
|
4529
|
+
await scoped.connect(name2, sourceScope(id));
|
|
4258
4530
|
} catch (error) {
|
|
4259
4531
|
await activateTrace(`post-sync connect failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4260
4532
|
}
|
|
@@ -4269,7 +4541,7 @@ function buildSourceActivateRoute(manager, helpers) {
|
|
|
4269
4541
|
}
|
|
4270
4542
|
|
|
4271
4543
|
// lib/server/api/routes.js
|
|
4272
|
-
function makeRoutes(manager, _cwd = process.cwd()) {
|
|
4544
|
+
function makeRoutes(manager, _cwd = process.cwd(), scoped = false) {
|
|
4273
4545
|
const handleError = (res, error) => {
|
|
4274
4546
|
let message;
|
|
4275
4547
|
try {
|
|
@@ -4286,7 +4558,7 @@ function makeRoutes(manager, _cwd = process.cwd()) {
|
|
|
4286
4558
|
await manager.setSession(cwdParam);
|
|
4287
4559
|
};
|
|
4288
4560
|
const helpers = { handleError, scopeParam, maybeSession };
|
|
4289
|
-
|
|
4561
|
+
const routes = [
|
|
4290
4562
|
buildConfigRoute(manager, helpers),
|
|
4291
4563
|
buildServersRoute(manager, helpers),
|
|
4292
4564
|
buildSessionRoute(manager, helpers),
|
|
@@ -4300,6 +4572,25 @@ function makeRoutes(manager, _cwd = process.cwd()) {
|
|
|
4300
4572
|
buildSourceActivateRoute(manager, helpers),
|
|
4301
4573
|
buildToolDisableRoute(manager, helpers)
|
|
4302
4574
|
];
|
|
4575
|
+
if (scoped || manager.forWorkspace === void 0)
|
|
4576
|
+
return routes;
|
|
4577
|
+
return routes.map((route, index) => ({ ...route, handler: async (req, res) => {
|
|
4578
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
4579
|
+
if (!url.searchParams.has("cwd") || route.path === ROUTES.session)
|
|
4580
|
+
return route.handler(req, res);
|
|
4581
|
+
const name2 = Object.keys(ROUTES).find((name3) => ROUTES[name3] === route.path);
|
|
4582
|
+
if (name2 !== void 0) {
|
|
4583
|
+
const fence = ROUTE_FENCE[name2];
|
|
4584
|
+
if (!fence.loopbackExempt.includes(req.method ?? "GET") && !guardLoopbackMethod(req, res, fence.guarded))
|
|
4585
|
+
return;
|
|
4586
|
+
}
|
|
4587
|
+
try {
|
|
4588
|
+
const view = await manager.forWorkspace(url.searchParams.get("cwd") ?? "");
|
|
4589
|
+
return await makeRoutes(view, _cwd, true)[index].handler(req, res);
|
|
4590
|
+
} catch (error) {
|
|
4591
|
+
handleError(res, error);
|
|
4592
|
+
}
|
|
4593
|
+
} }));
|
|
4303
4594
|
}
|
|
4304
4595
|
function uiConfigChangedFrame() {
|
|
4305
4596
|
return sseData({ type: SSE_FRAMES.uiConfigChanged });
|
|
@@ -4746,14 +5037,13 @@ function resolveCatalogInjection(decision, messages, supervisors, maxEntries = D
|
|
|
4746
5037
|
|
|
4747
5038
|
// lib/server/catalog/impl/cache-view/index.js
|
|
4748
5039
|
init_interface();
|
|
4749
|
-
init_interface2();
|
|
4750
5040
|
import { existsSync as existsSync5, statSync } from "node:fs";
|
|
4751
5041
|
|
|
4752
5042
|
// lib/server/catalog/impl/directory/index.js
|
|
4753
5043
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
4754
5044
|
import { existsSync as existsSync4 } from "node:fs";
|
|
4755
5045
|
import { mkdir as mkdir3, readFile as readFile3, rename as rename3, rm as rm3, writeFile as writeFile3 } from "node:fs/promises";
|
|
4756
|
-
import { dirname as
|
|
5046
|
+
import { dirname as dirname6 } from "node:path";
|
|
4757
5047
|
init_interface();
|
|
4758
5048
|
function shortCircuitsFresh(invalidated, entry) {
|
|
4759
5049
|
return !invalidated && isCatalogFresh(entry);
|
|
@@ -4769,7 +5059,7 @@ async function writeDirectoryCacheFile(file, data) {
|
|
|
4769
5059
|
await writeFileAtomic(file, data);
|
|
4770
5060
|
return;
|
|
4771
5061
|
}
|
|
4772
|
-
const dir =
|
|
5062
|
+
const dir = dirname6(file);
|
|
4773
5063
|
if (!existsSync4(dir))
|
|
4774
5064
|
await mkdir3(dir, { recursive: true });
|
|
4775
5065
|
const tmp = `${file}.${process.pid}.${Date.now().toString(36)}.${randomBytes3(6).toString("hex")}.tmp`;
|
|
@@ -5201,7 +5491,6 @@ function makeCatalogViewFor(host) {
|
|
|
5201
5491
|
}
|
|
5202
5492
|
|
|
5203
5493
|
// lib/server/catalog/search.js
|
|
5204
|
-
init_interface2();
|
|
5205
5494
|
init_interface();
|
|
5206
5495
|
function tokenize(text) {
|
|
5207
5496
|
const lowered = text.toLowerCase();
|
|
@@ -5254,7 +5543,7 @@ function scoreTerms(terms, haystack) {
|
|
|
5254
5543
|
}
|
|
5255
5544
|
return { score, matchedTerms };
|
|
5256
5545
|
}
|
|
5257
|
-
function searchCatalog(units, root, query, limit) {
|
|
5546
|
+
function searchCatalog(units, root, query, limit, serverEnabled) {
|
|
5258
5547
|
const { connection: { CATALOG_TTL_MS: CATALOG_TTL_MS2 }, workspace: { fullServerName: fullServerName2 } } = catalogPorts.get();
|
|
5259
5548
|
if (!units.has(root))
|
|
5260
5549
|
return { results: [], unavailable: [], truncated: false };
|
|
@@ -5265,6 +5554,8 @@ function searchCatalog(units, root, query, limit) {
|
|
|
5265
5554
|
const unavailable = [];
|
|
5266
5555
|
let truncated = false;
|
|
5267
5556
|
for (const [serverName, catalog] of servers) {
|
|
5557
|
+
if (serverEnabled?.(root, serverName) === false)
|
|
5558
|
+
continue;
|
|
5268
5559
|
if (catalog.unavailable !== void 0) {
|
|
5269
5560
|
unavailable.push({ server: fullServerName2(root, serverName), reason: catalog.unavailable });
|
|
5270
5561
|
continue;
|
|
@@ -5294,14 +5585,14 @@ function searchCatalog(units, root, query, limit) {
|
|
|
5294
5585
|
}
|
|
5295
5586
|
return { results, unavailable, truncated };
|
|
5296
5587
|
}
|
|
5297
|
-
function searchCatalogMulti(units, roots, query, limit) {
|
|
5588
|
+
function searchCatalogMulti(units, roots, query, limit, serverEnabled) {
|
|
5298
5589
|
if (roots.length === 1) {
|
|
5299
|
-
return searchCatalog(units, roots[0], query, limit);
|
|
5590
|
+
return searchCatalog(units, roots[0], query, limit, serverEnabled);
|
|
5300
5591
|
}
|
|
5301
5592
|
const results = [];
|
|
5302
5593
|
const unavailable = [];
|
|
5303
5594
|
for (const root of roots) {
|
|
5304
|
-
const single = searchCatalog(units, root, query, limit);
|
|
5595
|
+
const single = searchCatalog(units, root, query, limit, serverEnabled);
|
|
5305
5596
|
results.push(...single.results);
|
|
5306
5597
|
unavailable.push(...single.unavailable);
|
|
5307
5598
|
}
|
|
@@ -5344,7 +5635,7 @@ function resolveListRoots(roots, serverFilter) {
|
|
|
5344
5635
|
}
|
|
5345
5636
|
return void 0;
|
|
5346
5637
|
}
|
|
5347
|
-
function listCatalog(units, roots, serverFilter, toolLimit, emptyHint, disabledTools) {
|
|
5638
|
+
function listCatalog(units, roots, serverFilter, toolLimit, emptyHint, disabledTools, serverEnabled) {
|
|
5348
5639
|
const { workspace: { fullServerName: fullServerName2 } } = catalogPorts.get();
|
|
5349
5640
|
const safeLimit = safeToolLimit(toolLimit);
|
|
5350
5641
|
const rootSet = resolveListRoots(roots, serverFilter);
|
|
@@ -5361,6 +5652,8 @@ function listCatalog(units, roots, serverFilter, toolLimit, emptyHint, disabledT
|
|
|
5361
5652
|
if (rootCatalog === void 0)
|
|
5362
5653
|
continue;
|
|
5363
5654
|
for (const [serverName, catalog] of rootCatalog) {
|
|
5655
|
+
if (serverEnabled?.(root, serverName) === false)
|
|
5656
|
+
continue;
|
|
5364
5657
|
if (!matchesServerFilter(serverName, root, serverFilter, fullServerName2))
|
|
5365
5658
|
continue;
|
|
5366
5659
|
const built = buildListServerEntry(serverName, root, catalog, unit.userDisabled, safeLimit, disabledTools, fullServerName2);
|
|
@@ -5506,135 +5799,7 @@ __export(interface_exports3, {
|
|
|
5506
5799
|
panelTopForAnchor: () => panelTopForAnchor,
|
|
5507
5800
|
parseClaudeJson: () => parseClaudeJson
|
|
5508
5801
|
});
|
|
5509
|
-
|
|
5510
|
-
// lib/server/config/normalize.js
|
|
5511
|
-
init_interface();
|
|
5512
|
-
var RECONNECT_KEYS = /* @__PURE__ */ new Set(["enabled", "initialDelayMs", "maxDelayMs", "maxAttempts"]);
|
|
5513
|
-
var MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
5514
|
-
var RECONNECT_DEFAULTS = {
|
|
5515
|
-
enabled: true,
|
|
5516
|
-
initialDelayMs: 500,
|
|
5517
|
-
maxDelayMs: 3e4,
|
|
5518
|
-
maxAttempts: 10
|
|
5519
|
-
};
|
|
5520
|
-
function assertReconnectDelay(value, path) {
|
|
5521
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0 || value > MAX_TIMER_DELAY_MS) {
|
|
5522
|
-
throw new Error(`${path} must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`);
|
|
5523
|
-
}
|
|
5524
|
-
}
|
|
5525
|
-
function assertReconnectRelation(out) {
|
|
5526
|
-
const initialDelayMs = out.initialDelayMs ?? RECONNECT_DEFAULTS.initialDelayMs;
|
|
5527
|
-
const maxDelayMs = out.maxDelayMs ?? RECONNECT_DEFAULTS.maxDelayMs;
|
|
5528
|
-
if (initialDelayMs > maxDelayMs) {
|
|
5529
|
-
throw new Error("reconnect.initialDelayMs must be less than or equal to maxDelayMs");
|
|
5530
|
-
}
|
|
5531
|
-
const { maxAttempts } = out;
|
|
5532
|
-
if (maxAttempts !== void 0 && (typeof maxAttempts !== "number" || !Number.isInteger(maxAttempts) || maxAttempts < 1)) {
|
|
5533
|
-
throw new Error("reconnect.maxAttempts must be a positive integer");
|
|
5534
|
-
}
|
|
5535
|
-
}
|
|
5536
|
-
function normalizeReconnect(raw) {
|
|
5537
|
-
if (raw === void 0 || raw === null)
|
|
5538
|
-
return {};
|
|
5539
|
-
if (typeof raw !== "object" || Array.isArray(raw))
|
|
5540
|
-
throw new Error("reconnect must be an object");
|
|
5541
|
-
const out = {};
|
|
5542
|
-
for (const [key, value] of Object.entries(raw)) {
|
|
5543
|
-
if (RECONNECT_KEYS.has(key))
|
|
5544
|
-
out[key] = value;
|
|
5545
|
-
}
|
|
5546
|
-
assertReconnectFields(out);
|
|
5547
|
-
return out;
|
|
5548
|
-
}
|
|
5549
|
-
function assertReconnectFields(out) {
|
|
5550
|
-
if (out.initialDelayMs !== void 0)
|
|
5551
|
-
assertReconnectDelay(out.initialDelayMs, "reconnect.initialDelayMs");
|
|
5552
|
-
if (out.maxDelayMs !== void 0)
|
|
5553
|
-
assertReconnectDelay(out.maxDelayMs, "reconnect.maxDelayMs");
|
|
5554
|
-
if (out.enabled !== void 0 && typeof out.enabled !== "boolean") {
|
|
5555
|
-
throw new Error("reconnect.enabled must be a boolean");
|
|
5556
|
-
}
|
|
5557
|
-
assertReconnectRelation(out);
|
|
5558
|
-
}
|
|
5559
|
-
function assertTransportRequired(src, transport) {
|
|
5560
|
-
if (transport === "stdio") {
|
|
5561
|
-
if (typeof src.command !== "string" || src.command.trim() === "") {
|
|
5562
|
-
throw new Error("stdio server requires a command");
|
|
5563
|
-
}
|
|
5564
|
-
return;
|
|
5565
|
-
}
|
|
5566
|
-
if (typeof src.url !== "string" || src.url.trim() === "") {
|
|
5567
|
-
throw new Error("streamable-http server requires a url");
|
|
5568
|
-
}
|
|
5569
|
-
let parsed;
|
|
5570
|
-
try {
|
|
5571
|
-
parsed = new URL(src.url);
|
|
5572
|
-
} catch {
|
|
5573
|
-
throw new Error(`invalid url: ${src.url}`);
|
|
5574
|
-
}
|
|
5575
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
5576
|
-
throw new Error(`unsupported protocol: ${parsed.protocol}//`);
|
|
5577
|
-
}
|
|
5578
|
-
}
|
|
5579
|
-
function applyTransportFields(server, src, transport) {
|
|
5580
|
-
if (transport === "stdio") {
|
|
5581
|
-
server.command = src.command;
|
|
5582
|
-
if (Array.isArray(src.args))
|
|
5583
|
-
server.args = src.args.map(String);
|
|
5584
|
-
if (typeof src.cwd === "string" && src.cwd !== "")
|
|
5585
|
-
server.cwd = src.cwd;
|
|
5586
|
-
if (typeof src.env === "object" && src.env !== null) {
|
|
5587
|
-
server.env = Object.fromEntries(Object.entries(src.env).map(([key, value]) => [
|
|
5588
|
-
key,
|
|
5589
|
-
String(value)
|
|
5590
|
-
]));
|
|
5591
|
-
}
|
|
5592
|
-
return;
|
|
5593
|
-
}
|
|
5594
|
-
server.url = src.url;
|
|
5595
|
-
if (typeof src.headers === "object" && src.headers !== null) {
|
|
5596
|
-
server.headers = Object.fromEntries(Object.entries(src.headers).map(([key, value]) => [
|
|
5597
|
-
key,
|
|
5598
|
-
String(value)
|
|
5599
|
-
]));
|
|
5600
|
-
}
|
|
5601
|
-
}
|
|
5602
|
-
function normalizeToolCallTimeoutMs(value) {
|
|
5603
|
-
return typeof value === "number" && value > 0 ? Math.floor(value) : DEFAULT_TOOL_CALL_TIMEOUT_MS;
|
|
5604
|
-
}
|
|
5605
|
-
function normalizeDescription(value) {
|
|
5606
|
-
if (typeof value !== "string")
|
|
5607
|
-
return void 0;
|
|
5608
|
-
const trimmed = value.trim();
|
|
5609
|
-
return trimmed === "" ? void 0 : trimmed;
|
|
5610
|
-
}
|
|
5611
|
-
function normalizeServer(input) {
|
|
5612
|
-
if (typeof input !== "object" || input === null)
|
|
5613
|
-
throw new Error("server config must be an object");
|
|
5614
|
-
const src = input;
|
|
5615
|
-
const name2 = String(src.name ?? "").trim();
|
|
5616
|
-
if (!SERVER_NAME_PATTERN.test(name2)) {
|
|
5617
|
-
throw new Error(`server name must match ${SERVER_NAME_PATTERN.source}`);
|
|
5618
|
-
}
|
|
5619
|
-
const transport = src.transport === "streamable-http" ? "streamable-http" : src.transport === "stdio" ? "stdio" : void 0;
|
|
5620
|
-
if (transport === void 0)
|
|
5621
|
-
throw new Error('transport must be "stdio" or "streamable-http"');
|
|
5622
|
-
assertTransportRequired(src, transport);
|
|
5623
|
-
const server = {
|
|
5624
|
-
name: name2,
|
|
5625
|
-
transport,
|
|
5626
|
-
enabled: src.enabled !== false,
|
|
5627
|
-
toolCallTimeoutMs: normalizeToolCallTimeoutMs(src.toolCallTimeoutMs),
|
|
5628
|
-
reconnect: normalizeReconnect(src.reconnect),
|
|
5629
|
-
// 能力目录的自定义描述(用户手写;MCP 协议无服务器级自描述,完整保留不截断)。
|
|
5630
|
-
description: normalizeDescription(src.description)
|
|
5631
|
-
};
|
|
5632
|
-
if (typeof src.source === "string" && /^[a-z-]+$/.test(src.source)) {
|
|
5633
|
-
server.source = src.source;
|
|
5634
|
-
}
|
|
5635
|
-
applyTransportFields(server, src, transport);
|
|
5636
|
-
return server;
|
|
5637
|
-
}
|
|
5802
|
+
init_normalize();
|
|
5638
5803
|
|
|
5639
5804
|
// lib/server/config/impl/env/index.js
|
|
5640
5805
|
var SECRET_ENV_NAME = /(KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)/i;
|
|
@@ -5833,9 +5998,7 @@ init_import();
|
|
|
5833
5998
|
|
|
5834
5999
|
// lib/server/config/config-schema.js
|
|
5835
6000
|
var import_schemastery = __toESM(require_lib2(), 1);
|
|
5836
|
-
init_interface2();
|
|
5837
6001
|
init_interface();
|
|
5838
|
-
init_interface2();
|
|
5839
6002
|
var DEFAULT_UI_CONFIG = {
|
|
5840
6003
|
position: "top-right",
|
|
5841
6004
|
offset: { x: 8, y: 8, blankY: 40 },
|
|
@@ -6201,7 +6364,6 @@ var MountLedger = class {
|
|
|
6201
6364
|
var mountLedger = new MountLedger();
|
|
6202
6365
|
|
|
6203
6366
|
// lib/server/servers/lifecycle/impl/state/index.js
|
|
6204
|
-
init_interface2();
|
|
6205
6367
|
function projectServerState(id, input) {
|
|
6206
6368
|
if (!input.enabled || input.userDisabled)
|
|
6207
6369
|
return SERVER_STATES.disabled;
|
|
@@ -6224,7 +6386,6 @@ function settledState(input, id) {
|
|
|
6224
6386
|
|
|
6225
6387
|
// lib/server/servers/lifecycle/impl/timeout/index.js
|
|
6226
6388
|
init_interface();
|
|
6227
|
-
init_interface2();
|
|
6228
6389
|
|
|
6229
6390
|
// lib/server/servers/lifecycle/impl/logs/index.js
|
|
6230
6391
|
init_interface();
|
|
@@ -6696,7 +6857,6 @@ function withTimeout(promise, ms, message, signal) {
|
|
|
6696
6857
|
}
|
|
6697
6858
|
|
|
6698
6859
|
// lib/server/pipeline/impl/authorize/index.js
|
|
6699
|
-
init_interface2();
|
|
6700
6860
|
function globMatch(pattern, name2) {
|
|
6701
6861
|
if (pattern === "*")
|
|
6702
6862
|
return true;
|
|
@@ -6834,7 +6994,6 @@ var MAX_TOTAL_CATALOG_BYTES = 256 * 1024;
|
|
|
6834
6994
|
var LIST_MAX_TOOLS_PER_SERVER = 500;
|
|
6835
6995
|
|
|
6836
6996
|
// lib/server/connection/runtime/middleware.js
|
|
6837
|
-
init_interface2();
|
|
6838
6997
|
function connectWrappedServer(unit, root, serverName, server, host) {
|
|
6839
6998
|
if (!Array.isArray(server.toolDefinitions))
|
|
6840
6999
|
return false;
|
|
@@ -6976,6 +7135,10 @@ var McpMiddleware = class {
|
|
|
6976
7135
|
this.host = host;
|
|
6977
7136
|
this.units = /* @__PURE__ */ new Map();
|
|
6978
7137
|
}
|
|
7138
|
+
/** Authorization belongs to the caller workspace, independently of the shared connection root. */
|
|
7139
|
+
serverEnabledFor(root, target, name2) {
|
|
7140
|
+
return this.host.serverEnabledFor?.(root, target, name2) !== false;
|
|
7141
|
+
}
|
|
6979
7142
|
/** 读取/创建 root 的单元(root 无项目标记 → undefined)。 */
|
|
6980
7143
|
async projectUnitFor(root) {
|
|
6981
7144
|
if (root === void 0)
|
|
@@ -6985,6 +7148,9 @@ var McpMiddleware = class {
|
|
|
6985
7148
|
const servers = await this.host.projectServersFor(root);
|
|
6986
7149
|
if (servers === void 0)
|
|
6987
7150
|
return void 0;
|
|
7151
|
+
const existing = this.units.get(root);
|
|
7152
|
+
if (existing !== void 0)
|
|
7153
|
+
return existing;
|
|
6988
7154
|
unit = {
|
|
6989
7155
|
root,
|
|
6990
7156
|
connections: /* @__PURE__ */ new Map(),
|
|
@@ -7039,9 +7205,11 @@ var McpMiddleware = class {
|
|
|
7039
7205
|
* 全部吞掉,且旧 attempt 收敛时命中让位/disposed 守卫静默返回、无人补连 → 这段时间内
|
|
7040
7206
|
* 该服务器无法重连。拆除时必须同步废弃标记;旧 attempt 稍后收敛由 connectInternal
|
|
7041
7207
|
* 的让位/disposed 守卫兜底,无副作用。 */
|
|
7042
|
-
abandonInFlight(serverName) {
|
|
7043
|
-
for (const unit of this.units.values())
|
|
7044
|
-
unit.
|
|
7208
|
+
abandonInFlight(serverName, root) {
|
|
7209
|
+
for (const unit of this.units.values()) {
|
|
7210
|
+
if (root === void 0 || unit.root === root)
|
|
7211
|
+
unit.inFlight.delete(serverName);
|
|
7212
|
+
}
|
|
7045
7213
|
}
|
|
7046
7214
|
async connectInternal(root, serverName, opts = {}) {
|
|
7047
7215
|
const unit = this.units.get(root);
|
|
@@ -7197,7 +7365,15 @@ var McpMiddleware = class {
|
|
|
7197
7365
|
*/
|
|
7198
7366
|
async callTool(fullName, toolRaw, rawArgs, signal, identity) {
|
|
7199
7367
|
const { dispatch, pipeline, workspace, catalog: catalogPort } = runtimePorts.get();
|
|
7200
|
-
const
|
|
7368
|
+
const parsedTarget = workspace.parseFullServerName(fullName);
|
|
7369
|
+
const catalogRoot = parsedTarget?.root;
|
|
7370
|
+
if (parsedTarget !== void 0 && this.host.serverEnabledFor !== void 0) {
|
|
7371
|
+
const cwd = identity.agent?.session?.header?.cwd;
|
|
7372
|
+
const callerRoot = await this.host.normalizedProjectRoot(cwd) ?? MIDDLEWARE_GLOBAL_ROOT;
|
|
7373
|
+
if (!this.serverEnabledFor(callerRoot, parsedTarget.root, parsedTarget.server)) {
|
|
7374
|
+
throw new Error(`ws_mcp_call: server ${fullName} 已在当前工作区停用`);
|
|
7375
|
+
}
|
|
7376
|
+
}
|
|
7201
7377
|
const registeredNameFor = (id, tool) => publicToolName(id, tool);
|
|
7202
7378
|
const execute = (input) => this.host.ctx.tools.execute(input);
|
|
7203
7379
|
const parent = identity.parent;
|
|
@@ -7341,7 +7517,7 @@ __export(interface_exports8, {
|
|
|
7341
7517
|
init_interface();
|
|
7342
7518
|
import { createHash as createHash3, randomBytes as randomBytes4 } from "node:crypto";
|
|
7343
7519
|
import { mkdirSync as mkdirSync2, rmSync, writeFileSync as writeFileSync2, renameSync, existsSync as existsSync6, readFileSync as readFileSync2 } from "node:fs";
|
|
7344
|
-
import { dirname as
|
|
7520
|
+
import { dirname as dirname7, resolve as resolve2 } from "node:path";
|
|
7345
7521
|
var EMPTY_SEARCH_BUCKET = "<empty>";
|
|
7346
7522
|
var SEARCH_BUCKET_PREFIX = "h:";
|
|
7347
7523
|
var SEARCH_BUCKET_HASH_LENGTH = 12;
|
|
@@ -7608,7 +7784,7 @@ var McpStatsCollector = class {
|
|
|
7608
7784
|
if (!this.isDirty || !this.enabled)
|
|
7609
7785
|
return;
|
|
7610
7786
|
try {
|
|
7611
|
-
const dir =
|
|
7787
|
+
const dir = dirname7(this.filePath);
|
|
7612
7788
|
if (!existsSync6(dir)) {
|
|
7613
7789
|
mkdirSync2(dir, mkdirOptionsFor(dir));
|
|
7614
7790
|
}
|
|
@@ -7659,7 +7835,7 @@ init_interface();
|
|
|
7659
7835
|
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
7660
7836
|
import { mkdir as mkdir4, readFile as readFile4, rename as rename4, rm as rm4, stat, writeFile as writeFile4 } from "node:fs/promises";
|
|
7661
7837
|
import { existsSync as existsSync7 } from "node:fs";
|
|
7662
|
-
import { dirname as
|
|
7838
|
+
import { dirname as dirname8 } from "node:path";
|
|
7663
7839
|
var saveChains = /* @__PURE__ */ new Map();
|
|
7664
7840
|
var windowsRenameRetryDelays = [50, 100, 150, 200, 250];
|
|
7665
7841
|
function isRetryableWindowsRenameError(error) {
|
|
@@ -7747,7 +7923,7 @@ var McpStore = class {
|
|
|
7747
7923
|
if (await this.changedSince(baseline.mtimeMs, baseline.snapshot)) {
|
|
7748
7924
|
throw externalModificationError(this.path);
|
|
7749
7925
|
}
|
|
7750
|
-
const dir =
|
|
7926
|
+
const dir = dirname8(this.path);
|
|
7751
7927
|
if (!existsSync7(dir))
|
|
7752
7928
|
await mkdir4(dir, { recursive: true });
|
|
7753
7929
|
let mode;
|
|
@@ -7826,7 +8002,7 @@ init_interface();
|
|
|
7826
8002
|
import { createHash as createHash4, randomBytes as randomBytes6 } from "node:crypto";
|
|
7827
8003
|
import { mkdir as mkdir5, rename as rename5, rm as rm5, writeFile as writeFile5 } from "node:fs/promises";
|
|
7828
8004
|
import { existsSync as existsSync8 } from "node:fs";
|
|
7829
|
-
import { dirname as
|
|
8005
|
+
import { dirname as dirname9 } from "node:path";
|
|
7830
8006
|
async function writeStateFile(file, data) {
|
|
7831
8007
|
let registered = true;
|
|
7832
8008
|
try {
|
|
@@ -7838,7 +8014,7 @@ async function writeStateFile(file, data) {
|
|
|
7838
8014
|
await writeFileAtomic(file, data);
|
|
7839
8015
|
return;
|
|
7840
8016
|
}
|
|
7841
|
-
const dir =
|
|
8017
|
+
const dir = dirname9(file);
|
|
7842
8018
|
if (!existsSync8(dir))
|
|
7843
8019
|
await mkdir5(dir, { recursive: true });
|
|
7844
8020
|
const tmp = `${file}.${process.pid}.${Date.now().toString(36)}.${randomBytes6(6).toString("hex")}.tmp`;
|
|
@@ -8016,7 +8192,6 @@ var injectPorts = new InjectPorts();
|
|
|
8016
8192
|
|
|
8017
8193
|
// lib/server/inject/middleware-register.js
|
|
8018
8194
|
init_interface();
|
|
8019
|
-
init_interface2();
|
|
8020
8195
|
|
|
8021
8196
|
// lib/server/inject/impl/image-admission/index.js
|
|
8022
8197
|
var IMAGE_MEDIA_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif"];
|
|
@@ -8221,6 +8396,10 @@ function visibleProjectServers(root) {
|
|
|
8221
8396
|
const { catalog } = injectPorts.get();
|
|
8222
8397
|
return [...new Set(catalog.catalogDirectory.serverNamesFor(root))].sort();
|
|
8223
8398
|
}
|
|
8399
|
+
function serverVisible(mw, root, fullName) {
|
|
8400
|
+
const parsed = injectPorts.get().workspace.parseFullServerName(fullName);
|
|
8401
|
+
return parsed === void 0 || mw.serverEnabledFor?.(root, parsed.root, parsed.server) !== false;
|
|
8402
|
+
}
|
|
8224
8403
|
async function waitForDiscovery(unit) {
|
|
8225
8404
|
const { pipeline: { withTimeout: withTimeout2 } } = injectPorts.get();
|
|
8226
8405
|
const inflight = [...unit.inFlight.values()];
|
|
@@ -8297,9 +8476,13 @@ async function executeSearch(toolCtx, args, exec) {
|
|
|
8297
8476
|
await waitForDiscovery(visibleUnit);
|
|
8298
8477
|
}
|
|
8299
8478
|
const { catalog: { searchCatalogMulti: searchCatalogMulti2 } } = injectPorts.get();
|
|
8300
|
-
const { results, unavailable, truncated } = searchCatalogMulti2(toolCtx.mw.units, roots, query, limit);
|
|
8479
|
+
const { results, unavailable, truncated } = searchCatalogMulti2(toolCtx.mw.units, roots, query, limit, (target, name2) => toolCtx.mw.serverEnabledFor?.(root, target, name2) !== false);
|
|
8301
8480
|
const filtered = serverFilter === void 0 ? results : results.filter((hit) => hit.server === serverFilter);
|
|
8302
|
-
return {
|
|
8481
|
+
return {
|
|
8482
|
+
results: filtered.filter((hit) => serverVisible(toolCtx.mw, root, hit.server)),
|
|
8483
|
+
unavailable: unavailable.filter((entry) => serverVisible(toolCtx.mw, root, entry.server)),
|
|
8484
|
+
truncated
|
|
8485
|
+
};
|
|
8303
8486
|
}
|
|
8304
8487
|
function buildSearchTool(toolCtx) {
|
|
8305
8488
|
return {
|
|
@@ -8402,6 +8585,8 @@ async function executeCall(toolCtx, args, exec) {
|
|
|
8402
8585
|
const { server, tool, arguments: callArguments } = parseCallParams(args);
|
|
8403
8586
|
if (server === "" || tool === "")
|
|
8404
8587
|
throw new Error("ws_mcp_call: server 与 tool 均为必填");
|
|
8588
|
+
if (!serverVisible(toolCtx.mw, root, server))
|
|
8589
|
+
throw new Error(`ws_mcp_call: server ${server} 已在当前工作区停用`);
|
|
8405
8590
|
const { parsed, unit } = await assertCallableTarget(toolCtx, server, root);
|
|
8406
8591
|
await toolCtx.mw.ensureConnected(unit.root, parsed.server);
|
|
8407
8592
|
const startTime = Date.now();
|
|
@@ -8534,7 +8719,7 @@ async function resolveListWithoutUnit(mw, root, serverFilter, toolLimit) {
|
|
|
8534
8719
|
const globalUnit = await mw.projectUnitFor("@global");
|
|
8535
8720
|
if (globalUnit !== void 0)
|
|
8536
8721
|
await waitForDiscovery(globalUnit);
|
|
8537
|
-
return listCatalog2(mw.units, ["@global"], serverFilter, toolLimit, "当前工作空间没有可用 MCP 服务器(项目级与全局均未发现;若刚添加配置,请稍后重试)", mw.disabledTools);
|
|
8722
|
+
return listCatalog2(mw.units, ["@global"], serverFilter, toolLimit, "当前工作空间没有可用 MCP 服务器(项目级与全局均未发现;若刚添加配置,请稍后重试)", mw.disabledTools, (target, name2) => mw.serverEnabledFor?.(root, target, name2) !== false);
|
|
8538
8723
|
}
|
|
8539
8724
|
async function executeList(toolCtx, args, exec) {
|
|
8540
8725
|
const root = await toolCtx.resolveRoot(exec.agent);
|
|
@@ -8555,7 +8740,7 @@ async function executeList(toolCtx, args, exec) {
|
|
|
8555
8740
|
await waitForDiscovery(visibleUnit);
|
|
8556
8741
|
}
|
|
8557
8742
|
const { catalog: { listCatalog: listCatalog2 } } = injectPorts.get();
|
|
8558
|
-
const result = listCatalog2(toolCtx.mw.units, roots, serverFilter, toolLimit, "", toolCtx.mw.disabledTools);
|
|
8743
|
+
const result = listCatalog2(toolCtx.mw.units, roots, serverFilter, toolLimit, "", toolCtx.mw.disabledTools, (target, name2) => toolCtx.mw.serverEnabledFor?.(root, target, name2) !== false);
|
|
8559
8744
|
if (result.servers.length === 0) {
|
|
8560
8745
|
result.message = resolveEmptyListMessage(serverFilter, toolCtx.mw, root);
|
|
8561
8746
|
}
|
|
@@ -8636,6 +8821,8 @@ async function executeDetail(toolCtx, args, exec) {
|
|
|
8636
8821
|
toolCtx.stats.recordDetail(parsed?.server ?? server, tool);
|
|
8637
8822
|
}
|
|
8638
8823
|
const targetRoot = await checkMiddlewareRoot("ws_mcp_detail", server, root);
|
|
8824
|
+
if (!serverVisible(toolCtx.mw, root, server))
|
|
8825
|
+
throw new Error(`ws_mcp_detail: server ${server} 已在当前工作区停用`);
|
|
8639
8826
|
if (targetRoot === void 0) {
|
|
8640
8827
|
throw new Error(`ws_mcp_detail: server ${JSON.stringify(server)} 不属于当前工作空间 ${JSON.stringify(root)};路由一致性校验失败(防跨空间串台)`);
|
|
8641
8828
|
}
|
|
@@ -8831,7 +9018,7 @@ __export(interface_exports10, {
|
|
|
8831
9018
|
|
|
8832
9019
|
// lib/vendor/shared/upgrade-chain.js
|
|
8833
9020
|
import { existsSync as existsSync9, readFileSync as readFileSync3 } from "node:fs";
|
|
8834
|
-
import { dirname as
|
|
9021
|
+
import { dirname as dirname10, join as join6 } from "node:path";
|
|
8835
9022
|
var UNKNOWN_VERSION = "0.0.0";
|
|
8836
9023
|
var PACKAGE_ROOT_MAX_DEPTH = 8;
|
|
8837
9024
|
function selectPendingSteps(steps, recorded) {
|
|
@@ -8886,9 +9073,9 @@ function parseVersion(text) {
|
|
|
8886
9073
|
function packageRootFrom(fromDir) {
|
|
8887
9074
|
let current = fromDir;
|
|
8888
9075
|
for (let depth = 0; depth < PACKAGE_ROOT_MAX_DEPTH; depth += 1) {
|
|
8889
|
-
if (existsSync9(
|
|
9076
|
+
if (existsSync9(join6(current, "package.json")))
|
|
8890
9077
|
return current;
|
|
8891
|
-
const parent =
|
|
9078
|
+
const parent = dirname10(current);
|
|
8892
9079
|
if (parent === current)
|
|
8893
9080
|
return void 0;
|
|
8894
9081
|
current = parent;
|
|
@@ -8900,7 +9087,7 @@ function pluginVersion(fromDir) {
|
|
|
8900
9087
|
if (root === void 0)
|
|
8901
9088
|
return UNKNOWN_VERSION;
|
|
8902
9089
|
try {
|
|
8903
|
-
const parsed = JSON.parse(readFileSync3(
|
|
9090
|
+
const parsed = JSON.parse(readFileSync3(join6(root, "package.json"), "utf8"));
|
|
8904
9091
|
if (typeof parsed !== "object" || parsed === null || !("version" in parsed)) {
|
|
8905
9092
|
return UNKNOWN_VERSION;
|
|
8906
9093
|
}
|
|
@@ -8961,7 +9148,7 @@ function createUpgradeRunner(label, run) {
|
|
|
8961
9148
|
}
|
|
8962
9149
|
|
|
8963
9150
|
// lib/server/upgrade/impl/chain/index.js
|
|
8964
|
-
import { dirname as
|
|
9151
|
+
import { dirname as dirname11 } from "node:path";
|
|
8965
9152
|
import { fileURLToPath } from "node:url";
|
|
8966
9153
|
|
|
8967
9154
|
// lib/vendor/shared/upgrade-tick.js
|
|
@@ -8971,7 +9158,7 @@ async function tickUpgradeVersion() {
|
|
|
8971
9158
|
// lib/server/upgrade/impl/steps/storage-layout.js
|
|
8972
9159
|
init_interface();
|
|
8973
9160
|
import { existsSync as existsSync10, readFileSync as readFileSync4, renameSync as renameSync2, statSync as statSync2 } from "node:fs";
|
|
8974
|
-
import { join as
|
|
9161
|
+
import { join as join7 } from "node:path";
|
|
8975
9162
|
var MIGRATED_SUFFIX = ".migrated.bak";
|
|
8976
9163
|
var EMPTY_CONFIG = `${JSON.stringify({ version: 1, servers: [] }, null, 2)}
|
|
8977
9164
|
`;
|
|
@@ -8987,7 +9174,7 @@ var LAYOUT = [
|
|
|
8987
9174
|
// 上一代包分区目录形态的全局配置(~/.dsh/dsh-mcp-manager/mcp.json)→ .config 单文件。
|
|
8988
9175
|
// 排在 dsh-mcp.json 之后:目标已存在时只归档不覆盖(settleOne 幂等语义)。
|
|
8989
9176
|
{
|
|
8990
|
-
legacy: () =>
|
|
9177
|
+
legacy: () => join7(mcpManagerHome(), "mcp.json"),
|
|
8991
9178
|
target: configFile,
|
|
8992
9179
|
initial: EMPTY_CONFIG,
|
|
8993
9180
|
takenOver: (deps) => deps.storePath !== ""
|
|
@@ -9080,7 +9267,7 @@ async function writeStoredVersion(version) {
|
|
|
9080
9267
|
}
|
|
9081
9268
|
|
|
9082
9269
|
// lib/server/upgrade/impl/chain/index.js
|
|
9083
|
-
var MODULE_DIR =
|
|
9270
|
+
var MODULE_DIR = dirname11(fileURLToPath(import.meta.url));
|
|
9084
9271
|
function runUpgradeChain2(deps) {
|
|
9085
9272
|
return runUpgradeChain({
|
|
9086
9273
|
label: "dsh-mcp-manager",
|
|
@@ -9427,9 +9614,6 @@ function startSdkErasure(args) {
|
|
|
9427
9614
|
});
|
|
9428
9615
|
}
|
|
9429
9616
|
|
|
9430
|
-
// lib/index.js
|
|
9431
|
-
init_interface2();
|
|
9432
|
-
|
|
9433
9617
|
// lib/server/workspace/interface.js
|
|
9434
9618
|
var interface_exports11 = {};
|
|
9435
9619
|
__export(interface_exports11, {
|
|
@@ -9446,10 +9630,8 @@ __export(interface_exports11, {
|
|
|
9446
9630
|
normalizedProjectRoot: () => normalizedProjectRoot,
|
|
9447
9631
|
parseFullServerName: () => parseFullServerName
|
|
9448
9632
|
});
|
|
9449
|
-
init_root_resolution();
|
|
9450
9633
|
|
|
9451
9634
|
// lib/server/workspace/impl/full-name/index.js
|
|
9452
|
-
init_interface2();
|
|
9453
9635
|
function fullServerName(root, server) {
|
|
9454
9636
|
return `@${root}/${server}`;
|
|
9455
9637
|
}
|
|
@@ -9481,14 +9663,10 @@ function normalizeToolName(serverName, toolName, caller = "ws_mcp_call") {
|
|
|
9481
9663
|
}
|
|
9482
9664
|
|
|
9483
9665
|
// lib/server/workspace/impl/scope/index.js
|
|
9484
|
-
init_interface2();
|
|
9485
9666
|
function normalizeScope(value) {
|
|
9486
9667
|
return value === SCOPE_PROJECT ? SCOPE_PROJECT : SCOPE_GLOBAL;
|
|
9487
9668
|
}
|
|
9488
9669
|
|
|
9489
|
-
// lib/server/workspace/interface.js
|
|
9490
|
-
init_interface2();
|
|
9491
|
-
|
|
9492
9670
|
// lib/server/workspace/impl/server-id/index.js
|
|
9493
9671
|
init_interface();
|
|
9494
9672
|
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
@@ -9533,7 +9711,6 @@ function makeServerIdTable(options = {}) {
|
|
|
9533
9711
|
}
|
|
9534
9712
|
|
|
9535
9713
|
// lib/index.js
|
|
9536
|
-
init_interface2();
|
|
9537
9714
|
installCatalog({ store: interface_exports9, connection: interface_exports7, workspace: interface_exports11 });
|
|
9538
9715
|
installPipeline({ workspace: interface_exports11 });
|
|
9539
9716
|
installInject({
|
|
@@ -9709,10 +9886,11 @@ async function setupConfigWatchersAsync(manager) {
|
|
|
9709
9886
|
const fs = await import("node:fs");
|
|
9710
9887
|
const watchers = [];
|
|
9711
9888
|
const watched = /* @__PURE__ */ new Set();
|
|
9712
|
-
const watchConfig = (dir) => {
|
|
9713
|
-
|
|
9889
|
+
const watchConfig = (dir, sources = false) => {
|
|
9890
|
+
const key = `${dir}:${sources}`;
|
|
9891
|
+
if (watched.has(key))
|
|
9714
9892
|
return;
|
|
9715
|
-
watched.add(
|
|
9893
|
+
watched.add(key);
|
|
9716
9894
|
let busy = false;
|
|
9717
9895
|
let rerun = false;
|
|
9718
9896
|
const run = () => {
|
|
@@ -9723,7 +9901,10 @@ async function setupConfigWatchersAsync(manager) {
|
|
|
9723
9901
|
busy = true;
|
|
9724
9902
|
void (async () => {
|
|
9725
9903
|
try {
|
|
9726
|
-
|
|
9904
|
+
if (sources)
|
|
9905
|
+
await manager.sourcesChanged?.();
|
|
9906
|
+
else
|
|
9907
|
+
await manager.refreshFromDisk();
|
|
9727
9908
|
} finally {
|
|
9728
9909
|
busy = false;
|
|
9729
9910
|
if (rerun) {
|
|
@@ -9734,8 +9915,14 @@ async function setupConfigWatchersAsync(manager) {
|
|
|
9734
9915
|
})();
|
|
9735
9916
|
};
|
|
9736
9917
|
try {
|
|
9737
|
-
const watcher = fs.watch(dir, { persistent: false }, () =>
|
|
9918
|
+
const watcher = fs.watch(dir, { persistent: false }, (_event, filename) => {
|
|
9919
|
+
const name2 = filename?.toString();
|
|
9920
|
+
if (name2 !== void 0 && (sources ? name2 !== "mcp.json" : name2 !== "dsh-mcp-manager.config"))
|
|
9921
|
+
return;
|
|
9922
|
+
run();
|
|
9923
|
+
});
|
|
9738
9924
|
watcher.on("error", () => {
|
|
9925
|
+
watched.delete(key);
|
|
9739
9926
|
const index = watchers.indexOf(watcher);
|
|
9740
9927
|
if (index >= 0)
|
|
9741
9928
|
watchers.splice(index, 1);
|
|
@@ -9746,14 +9933,26 @@ async function setupConfigWatchersAsync(manager) {
|
|
|
9746
9933
|
});
|
|
9747
9934
|
watchers.push(watcher);
|
|
9748
9935
|
} catch {
|
|
9936
|
+
watched.delete(key);
|
|
9749
9937
|
}
|
|
9750
9938
|
};
|
|
9751
|
-
watchConfig(
|
|
9939
|
+
watchConfig(dirname12(manager.store.path));
|
|
9940
|
+
for (const id of SOURCE_IDS) {
|
|
9941
|
+
const path = sourcePath(id);
|
|
9942
|
+
if (path !== null)
|
|
9943
|
+
watchConfig(dirname12(path), true);
|
|
9944
|
+
}
|
|
9752
9945
|
if (manager.projectStore !== void 0)
|
|
9753
|
-
watchConfig(
|
|
9946
|
+
watchConfig(dirname12(manager.projectStore.path));
|
|
9754
9947
|
const unwatchProject = manager.onStatus(() => {
|
|
9755
|
-
|
|
9756
|
-
watchConfig(
|
|
9948
|
+
for (const [root, store] of manager.projectStores) {
|
|
9949
|
+
watchConfig(dirname12(store.path));
|
|
9950
|
+
for (const id of SOURCE_IDS) {
|
|
9951
|
+
const path = sourcePath(id, root);
|
|
9952
|
+
if (path !== null)
|
|
9953
|
+
watchConfig(dirname12(path), true);
|
|
9954
|
+
}
|
|
9955
|
+
}
|
|
9757
9956
|
});
|
|
9758
9957
|
return () => {
|
|
9759
9958
|
unwatchProject();
|
|
@@ -9931,9 +10130,7 @@ async function assembleEnabledRuntime(ctx, manager, options, syncFromSettings, f
|
|
|
9931
10130
|
faces.logger.warn(`dsh-mcp-manager: catalog reprojection failed: ${String(error)}`);
|
|
9932
10131
|
});
|
|
9933
10132
|
});
|
|
9934
|
-
await manager.startAll();
|
|
9935
10133
|
await manager.loadCatalogCache();
|
|
9936
|
-
manager.reconcileServers();
|
|
9937
10134
|
manager.logger.info("dsh-mcp-manager: middleware assembled (single pool, all servers via middleware)");
|
|
9938
10135
|
syncFromSettings();
|
|
9939
10136
|
let disposeInjection = () => {
|
|
@@ -9942,27 +10139,93 @@ async function assembleEnabledRuntime(ctx, manager, options, syncFromSettings, f
|
|
|
9942
10139
|
disposeInjection = registerCatalogInjection(ctx, manager, options.catalogMaxEntries);
|
|
9943
10140
|
}
|
|
9944
10141
|
const disposeRoutes = ctx.effect(() => setupRoutesAndBroadcast(ctx, manager), "dsh-mcp-manager: routes");
|
|
9945
|
-
|
|
9946
|
-
|
|
10142
|
+
let watchCleanup = await setupConfigWatchersAsync(manager);
|
|
10143
|
+
const sourceViews = /* @__PURE__ */ new Map();
|
|
10144
|
+
manager.sourceSnapshot = function() {
|
|
10145
|
+
return sourceViews.get(this.projectRoot ?? "");
|
|
10146
|
+
};
|
|
10147
|
+
const prepared = /* @__PURE__ */ new Map();
|
|
10148
|
+
let stopped = false;
|
|
10149
|
+
const syncWorkspace = async (cwd) => {
|
|
9947
10150
|
const { syncSourcesIntoManager: syncSourcesIntoManager2 } = await Promise.resolve().then(() => (init_sources_sync(), sources_sync_exports));
|
|
9948
|
-
const
|
|
9949
|
-
const
|
|
9950
|
-
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
10151
|
+
const view = await manager.forWorkspace(cwd);
|
|
10152
|
+
const prefs = await loadSourcePrefsForSync();
|
|
10153
|
+
await syncSourcesIntoManager2(view, view.projectRoot, prefs);
|
|
10154
|
+
sourceViews.set(view.projectRoot ?? "", { sources: collectParsedSources(view.projectRoot), prefs });
|
|
10155
|
+
if (stopped)
|
|
10156
|
+
return;
|
|
10157
|
+
if (view.projectRoot === void 0)
|
|
10158
|
+
await manager.startAll();
|
|
10159
|
+
else
|
|
10160
|
+
await mw.projectUnitFor(view.projectRoot);
|
|
10161
|
+
manager.emitStatus();
|
|
10162
|
+
};
|
|
10163
|
+
manager.prepareWorkspace = async (cwd) => {
|
|
10164
|
+
const key = (await manager.forWorkspace(cwd)).projectRoot ?? "";
|
|
10165
|
+
let task = prepared.get(key);
|
|
10166
|
+
if (task === void 0) {
|
|
10167
|
+
task = (async () => {
|
|
10168
|
+
if (key !== "")
|
|
10169
|
+
await manager.prepareWorkspace?.();
|
|
10170
|
+
await syncWorkspace(key === "" ? void 0 : key);
|
|
10171
|
+
})();
|
|
10172
|
+
prepared.set(key, task);
|
|
10173
|
+
void task.catch(() => prepared.delete(key));
|
|
10174
|
+
}
|
|
10175
|
+
return task;
|
|
10176
|
+
};
|
|
10177
|
+
let sourceUpdates = Promise.resolve();
|
|
10178
|
+
manager.sourcesChanged = () => {
|
|
10179
|
+
sourceUpdates = sourceUpdates.catch(() => {
|
|
10180
|
+
}).then(async () => {
|
|
10181
|
+
await syncWorkspace();
|
|
10182
|
+
for (const cwd of prepared.keys()) {
|
|
10183
|
+
if (cwd !== "")
|
|
10184
|
+
await syncWorkspace(cwd);
|
|
9955
10185
|
}
|
|
9956
|
-
};
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
|
|
9960
|
-
|
|
9961
|
-
|
|
9962
|
-
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
10186
|
+
});
|
|
10187
|
+
return sourceUpdates;
|
|
10188
|
+
};
|
|
10189
|
+
const bootstrapTimer = setTimeout(() => {
|
|
10190
|
+
if (typeof ctx.get !== "function")
|
|
10191
|
+
return;
|
|
10192
|
+
void (async () => {
|
|
10193
|
+
await manager.prepareWorkspace?.();
|
|
10194
|
+
const registry = typeof ctx.get === "function" ? ctx.get("workspaceRegistry") : void 0;
|
|
10195
|
+
const workspaces = registry?.list?.() ?? [];
|
|
10196
|
+
for (const workspace of workspaces) {
|
|
10197
|
+
if (stopped)
|
|
10198
|
+
return;
|
|
10199
|
+
await manager.prepareWorkspace?.(workspace.path);
|
|
10200
|
+
await new Promise((resolve3) => setTimeout(resolve3, 0));
|
|
10201
|
+
}
|
|
10202
|
+
})().catch((error) => manager.logger.warn(`dsh-mcp-manager: background bootstrap failed: ${manager.redactError(error)}`));
|
|
10203
|
+
}, 0);
|
|
10204
|
+
bootstrapTimer.unref?.();
|
|
10205
|
+
const disposeWorkspaceWarmup = typeof ctx.inject === "function" ? ctx.inject(["workspaceRegistry"], (injected) => {
|
|
10206
|
+
const timer = setTimeout(() => {
|
|
10207
|
+
const registry = injected.get("workspaceRegistry");
|
|
10208
|
+
void (async () => {
|
|
10209
|
+
for (const workspace of registry?.list() ?? []) {
|
|
10210
|
+
if (stopped)
|
|
10211
|
+
return;
|
|
10212
|
+
await manager.prepareWorkspace?.(workspace.path);
|
|
10213
|
+
await new Promise((resolve3) => setTimeout(resolve3, 0));
|
|
10214
|
+
}
|
|
10215
|
+
})().catch((error) => manager.logger.warn(`dsh-mcp-manager: workspace warmup failed: ${manager.redactError(error)}`));
|
|
10216
|
+
}, 0);
|
|
10217
|
+
timer.unref?.();
|
|
10218
|
+
return () => clearTimeout(timer);
|
|
10219
|
+
}) : void 0;
|
|
10220
|
+
const stopWatchers = watchCleanup;
|
|
10221
|
+
watchCleanup = () => {
|
|
10222
|
+
stopped = true;
|
|
10223
|
+
clearTimeout(bootstrapTimer);
|
|
10224
|
+
manager.prepareWorkspace = void 0;
|
|
10225
|
+
manager.sourcesChanged = void 0;
|
|
10226
|
+
void disposeWorkspaceWarmup?.dispose?.();
|
|
10227
|
+
stopWatchers();
|
|
10228
|
+
};
|
|
9966
10229
|
let disposeSection = () => {
|
|
9967
10230
|
};
|
|
9968
10231
|
if (options.announceToAgent) {
|