@huanlin/dsh-plugin-mcp-manager 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -11
- package/lib/index.js +1229 -268
- package/lib/index.mjs +147 -13
- package/package.json +1 -1
package/lib/index.mjs
CHANGED
|
@@ -132,7 +132,7 @@ function configToData(config) {
|
|
|
132
132
|
}
|
|
133
133
|
/**
|
|
134
134
|
* 读全部 MCP 服务器行。配置块缺失/结构异常的行被跳过(不抛——读路径容忍)。
|
|
135
|
-
* 返回每行的 id 与解析出的 config
|
|
135
|
+
* 返回每行的 id 与解析出的 config(仅含存在的字段)+ entry-level disabled 标志。
|
|
136
136
|
*/
|
|
137
137
|
function listServers() {
|
|
138
138
|
const doc = readPatchDocument();
|
|
@@ -147,10 +147,13 @@ function listServers() {
|
|
|
147
147
|
} catch {
|
|
148
148
|
continue;
|
|
149
149
|
}
|
|
150
|
+
const disabledRaw = row.get("disabled");
|
|
151
|
+
const disabled = disabledRaw === true || disabledRaw === "true";
|
|
150
152
|
out.push({
|
|
151
153
|
id,
|
|
152
154
|
name: MCP_CLIENT_PACKAGE,
|
|
153
|
-
config
|
|
155
|
+
config,
|
|
156
|
+
disabled
|
|
154
157
|
});
|
|
155
158
|
}
|
|
156
159
|
return out;
|
|
@@ -277,10 +280,29 @@ function deleteServer(id, opts) {
|
|
|
277
280
|
console.log(`[dsh-mcp-manager] deleted server ${id}`);
|
|
278
281
|
return true;
|
|
279
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* 设置一个服务器的禁用状态。`disabled: true` 写在 entry level(与 id/name/config
|
|
285
|
+
* 同级),loader 跳过挂载该 mcp-client 实例 → 工具不注册(模型不可见),配置保留。
|
|
286
|
+
* 设为 false 时移除 `disabled` 字段(比 `disabled: false` 更干净),loader HMR
|
|
287
|
+
* 重新挂载实例。
|
|
288
|
+
*
|
|
289
|
+
* @returns true 设置成功;false 行不存在。
|
|
290
|
+
* @throws RegistryError 写后校验失败(已回滚)。
|
|
291
|
+
*/
|
|
292
|
+
function setServerDisabled(id, disabled, opts) {
|
|
293
|
+
const doc = readPatchDocument();
|
|
294
|
+
const target = collectMcpRows(doc).find((r) => r.id === id);
|
|
295
|
+
if (target === void 0) return false;
|
|
296
|
+
if (disabled) target.row.set("disabled", true);
|
|
297
|
+
else if (target.row.has("disabled")) target.row.delete("disabled");
|
|
298
|
+
writeAndValidate(doc, opts?.loadOverlayPatches);
|
|
299
|
+
console.log(`[dsh-mcp-manager] set server ${id} disabled=${disabled}`);
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
280
302
|
//#endregion
|
|
281
303
|
//#region src/tools.ts
|
|
282
304
|
/**
|
|
283
|
-
* MCP 管理工具(mcp_* ×
|
|
305
|
+
* MCP 管理工具(mcp_* ×5):agent 面的服务器注册表管理(对齐开发计划 §M4)。
|
|
284
306
|
* 与 GUI 面板写同一安装态(profile cordis.patch.yml 的 mcp-client insert 行),
|
|
285
307
|
* 配置 HMR 实时挂载——agent 调用后工具立即可用(若服务器连接成功)。
|
|
286
308
|
*
|
|
@@ -288,6 +310,8 @@ function deleteServer(id, opts) {
|
|
|
288
310
|
* - mcp_server_add:新增服务器(校验 + 写 insert 行 → HMR 挂载 mcp-client 实例)
|
|
289
311
|
* - mcp_server_update:整块替换 config(serverName 不变则工具名不变)
|
|
290
312
|
* - mcp_server_remove:移除行 → 工具随实例 dispose 注销
|
|
313
|
+
* - mcp_server_set_enabled:禁用/启用 entry-level disabled 字段(config 保留,
|
|
314
|
+
* 禁用时 loader 跳过挂载 → 工具不注册 → 模型不可见)
|
|
291
315
|
*
|
|
292
316
|
* 依赖注入(deps):避免与 index.ts 循环依赖。连接生命周期完全委托官方
|
|
293
317
|
* mcp-client——管理插件只写配置,不拉连接。
|
|
@@ -295,10 +319,11 @@ function deleteServer(id, opts) {
|
|
|
295
319
|
/** 把注册表行 + 运行态工具名投影为 agent 可见的规范视图。 */
|
|
296
320
|
function toServerView(row, toolNames, connectingSince) {
|
|
297
321
|
const prefix = `mcp__${row.config.serverName}__`;
|
|
298
|
-
const toolCount = toolNames.filter((n) => n.startsWith(prefix)).length;
|
|
322
|
+
const toolCount = row.disabled ? 0 : toolNames.filter((n) => n.startsWith(prefix)).length;
|
|
299
323
|
const endpoint = row.config.transport === "stdio" ? `${row.config.command ?? ""} ${(row.config.args ?? []).join(" ")}`.trim() : row.config.url ?? "";
|
|
300
324
|
let status;
|
|
301
|
-
if (
|
|
325
|
+
if (row.disabled) status = "disabled";
|
|
326
|
+
else if (toolCount > 0) status = "connected";
|
|
302
327
|
else {
|
|
303
328
|
const since = connectingSince.get(row.config.serverName);
|
|
304
329
|
status = since !== void 0 && Date.now() - since < 3e4 ? "connecting" : "disconnected";
|
|
@@ -309,6 +334,7 @@ function toServerView(row, toolNames, connectingSince) {
|
|
|
309
334
|
transport: row.config.transport,
|
|
310
335
|
endpoint,
|
|
311
336
|
toolCount,
|
|
337
|
+
disabled: row.disabled,
|
|
312
338
|
status
|
|
313
339
|
};
|
|
314
340
|
}
|
|
@@ -390,7 +416,7 @@ function createMcpTools(deps) {
|
|
|
390
416
|
return [
|
|
391
417
|
defineTool({
|
|
392
418
|
name: "mcp_server_list",
|
|
393
|
-
description: "List registered MCP servers and their live tool counts. Each server is an @deepseek-ai/dsh-mcp-client instance mounted from the profile cordis.patch.yml insert row. status: connected (tools registered), connecting (mcp-client mounting/handshaking within the post-write grace window), disconnected (0 tools past the grace window — failed/exhausted).",
|
|
419
|
+
description: "List registered MCP servers and their live tool counts. Each server is an @deepseek-ai/dsh-mcp-client instance mounted from the profile cordis.patch.yml insert row. status: connected (tools registered), connecting (mcp-client mounting/handshaking within the post-write grace window), disconnected (0 tools past the grace window — failed/exhausted), disabled (entry-level disabled=true — config preserved but loader skips mounting, tools not registered and invisible to the model).",
|
|
394
420
|
parameters: {},
|
|
395
421
|
output: {
|
|
396
422
|
schema: {
|
|
@@ -423,13 +449,18 @@ function createMcpTools(deps) {
|
|
|
423
449
|
type: "number",
|
|
424
450
|
required: true
|
|
425
451
|
},
|
|
452
|
+
disabled: {
|
|
453
|
+
type: "boolean",
|
|
454
|
+
required: true
|
|
455
|
+
},
|
|
426
456
|
status: {
|
|
427
457
|
type: "string",
|
|
428
458
|
required: true,
|
|
429
459
|
enum: [
|
|
430
460
|
"connected",
|
|
431
461
|
"connecting",
|
|
432
|
-
"disconnected"
|
|
462
|
+
"disconnected",
|
|
463
|
+
"disabled"
|
|
433
464
|
]
|
|
434
465
|
}
|
|
435
466
|
}
|
|
@@ -568,6 +599,61 @@ function createMcpTools(deps) {
|
|
|
568
599
|
message: `mcp_server_remove: removed "${id}" — mcp-client disposing, tools unregistering.`
|
|
569
600
|
};
|
|
570
601
|
}
|
|
602
|
+
}),
|
|
603
|
+
defineTool({
|
|
604
|
+
name: "mcp_server_set_enabled",
|
|
605
|
+
description: "Enable or disable an MCP server without deleting its config. Sets the entry-level `disabled` field (sibling of id/name/config) in the profile cordis.patch.yml. When disabled, the loader skips mounting the @deepseek-ai/dsh-mcp-client instance — its tools (mcp__<serverName>__*) are NOT registered and the model CANNOT see or call them. The config block is preserved verbatim, so re-enabling is a no-cost toggle (no re-entry of fields). Use this instead of mcp_server_remove when you want to temporarily hide a server from the model.",
|
|
606
|
+
parameters: {
|
|
607
|
+
id: {
|
|
608
|
+
type: "string",
|
|
609
|
+
required: true,
|
|
610
|
+
description: "The server insert-row id (e.g. mcp-github)."
|
|
611
|
+
},
|
|
612
|
+
enabled: {
|
|
613
|
+
type: "boolean",
|
|
614
|
+
required: true,
|
|
615
|
+
description: "true = mount the instance (tools visible); false = skip mounting (tools hidden, config preserved)."
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
output: {
|
|
619
|
+
schema: {
|
|
620
|
+
type: "object",
|
|
621
|
+
additionalProperties: false,
|
|
622
|
+
properties: {
|
|
623
|
+
ok: {
|
|
624
|
+
type: "boolean",
|
|
625
|
+
required: true
|
|
626
|
+
},
|
|
627
|
+
id: {
|
|
628
|
+
type: "string",
|
|
629
|
+
required: true
|
|
630
|
+
},
|
|
631
|
+
enabled: {
|
|
632
|
+
type: "boolean",
|
|
633
|
+
required: true
|
|
634
|
+
},
|
|
635
|
+
message: {
|
|
636
|
+
type: "string",
|
|
637
|
+
required: true
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
},
|
|
641
|
+
render: (_args, value) => [{
|
|
642
|
+
type: "text",
|
|
643
|
+
text: value.message
|
|
644
|
+
}]
|
|
645
|
+
},
|
|
646
|
+
async execute(args) {
|
|
647
|
+
const id = String(args.id);
|
|
648
|
+
const enabled = Boolean(args.enabled);
|
|
649
|
+
deps.setServerDisabled(id, !enabled);
|
|
650
|
+
return {
|
|
651
|
+
ok: true,
|
|
652
|
+
id,
|
|
653
|
+
enabled,
|
|
654
|
+
message: enabled ? `mcp_server_set_enabled: enabled "${id}" — mcp-client mounting via config HMR, tools will appear as mcp__<serverName>__* once the connection succeeds.` : `mcp_server_set_enabled: disabled "${id}" — mcp-client disposing, tools unregistering. Config preserved in patch file; re-enable anytime.`
|
|
655
|
+
};
|
|
656
|
+
}
|
|
571
657
|
})
|
|
572
658
|
];
|
|
573
659
|
}
|
|
@@ -599,8 +685,9 @@ function readBody(req) {
|
|
|
599
685
|
* 这是 P0 朴素推断(对齐开发计划 §4),非真实连接状态。
|
|
600
686
|
*/
|
|
601
687
|
const CONNECTING_GRACE_MS = 3e4;
|
|
602
|
-
/**
|
|
603
|
-
function computeStatus(toolCount, serverName, connectingSince) {
|
|
688
|
+
/** 计算一个服务器的四态状态。 */
|
|
689
|
+
function computeStatus(toolCount, serverName, connectingSince, disabled) {
|
|
690
|
+
if (disabled) return "disabled";
|
|
604
691
|
if (toolCount > 0) return "connected";
|
|
605
692
|
const since = connectingSince.get(serverName);
|
|
606
693
|
if (since !== void 0 && Date.now() - since < CONNECTING_GRACE_MS) return "connecting";
|
|
@@ -620,6 +707,10 @@ function apply(ctx) {
|
|
|
620
707
|
const markConnecting = (serverName) => {
|
|
621
708
|
connectingSince.set(serverName, Date.now());
|
|
622
709
|
};
|
|
710
|
+
/** 清除连接中标记(禁用时调用——不再处于连接中,而是已禁用)。 */
|
|
711
|
+
const clearConnecting = (serverName) => {
|
|
712
|
+
connectingSince.delete(serverName);
|
|
713
|
+
};
|
|
623
714
|
const mcpTools = createMcpTools({
|
|
624
715
|
listServers,
|
|
625
716
|
addServer: (config, opts) => {
|
|
@@ -635,6 +726,16 @@ function apply(ctx) {
|
|
|
635
726
|
markConnecting(config.serverName);
|
|
636
727
|
},
|
|
637
728
|
deleteServer: (id) => deleteServer(id, { loadOverlayPatches }),
|
|
729
|
+
setServerDisabled: (id, disabled) => {
|
|
730
|
+
setServerDisabled(id, disabled, { loadOverlayPatches });
|
|
731
|
+
if (disabled) {
|
|
732
|
+
const row = listServers().find((r) => r.id === id);
|
|
733
|
+
if (row !== void 0) clearConnecting(row.config.serverName);
|
|
734
|
+
} else {
|
|
735
|
+
const row = listServers().find((r) => r.id === id);
|
|
736
|
+
if (row !== void 0) markConnecting(row.config.serverName);
|
|
737
|
+
}
|
|
738
|
+
},
|
|
638
739
|
registeredToolNames: () => (ctx.tools?.schemas() ?? []).map((s) => s.name),
|
|
639
740
|
connectingSince,
|
|
640
741
|
selfId: "@huanlin/dsh-plugin-mcp-manager"
|
|
@@ -665,14 +766,15 @@ function apply(ctx) {
|
|
|
665
766
|
ok: true,
|
|
666
767
|
servers: listServers().map((row) => {
|
|
667
768
|
const prefix = `mcp__${row.config.serverName}__`;
|
|
668
|
-
const toolCount = toolNames.filter((n) => n.startsWith(prefix)).length;
|
|
769
|
+
const toolCount = row.disabled ? 0 : toolNames.filter((n) => n.startsWith(prefix)).length;
|
|
669
770
|
return {
|
|
670
771
|
id: row.id,
|
|
671
772
|
serverName: row.config.serverName,
|
|
672
773
|
transport: row.config.transport,
|
|
673
774
|
endpoint: row.config.transport === "stdio" ? `${row.config.command ?? ""} ${(row.config.args ?? []).join(" ")}`.trim() : row.config.url ?? "",
|
|
674
775
|
toolCount,
|
|
675
|
-
|
|
776
|
+
disabled: row.disabled,
|
|
777
|
+
status: computeStatus(toolCount, row.config.serverName, connectingSince, row.disabled),
|
|
676
778
|
config: row.config
|
|
677
779
|
};
|
|
678
780
|
})
|
|
@@ -713,12 +815,13 @@ function apply(ctx) {
|
|
|
713
815
|
ok: true,
|
|
714
816
|
status: listServers().map((row) => {
|
|
715
817
|
const prefix = `mcp__${row.config.serverName}__`;
|
|
716
|
-
const toolCount = toolNames.filter((n) => n.startsWith(prefix)).length;
|
|
818
|
+
const toolCount = row.disabled ? 0 : toolNames.filter((n) => n.startsWith(prefix)).length;
|
|
717
819
|
return {
|
|
718
820
|
id: row.id,
|
|
719
821
|
serverName: row.config.serverName,
|
|
720
822
|
toolCount,
|
|
721
|
-
|
|
823
|
+
disabled: row.disabled,
|
|
824
|
+
status: computeStatus(toolCount, row.config.serverName, connectingSince, row.disabled)
|
|
722
825
|
};
|
|
723
826
|
})
|
|
724
827
|
});
|
|
@@ -769,6 +872,37 @@ function apply(ctx) {
|
|
|
769
872
|
});
|
|
770
873
|
return;
|
|
771
874
|
}
|
|
875
|
+
if (method === "PATCH" && putMatch !== null) {
|
|
876
|
+
const id = decodeURIComponent(putMatch[1]);
|
|
877
|
+
const body = await readBody(req);
|
|
878
|
+
const parsed = JSON.parse(body);
|
|
879
|
+
if (parsed.disabled === void 0 || typeof parsed.disabled !== "boolean") {
|
|
880
|
+
jsonRes(400, {
|
|
881
|
+
ok: false,
|
|
882
|
+
message: "disabled (boolean) is required"
|
|
883
|
+
});
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
if (!setServerDisabled(id, parsed.disabled, { loadOverlayPatches })) {
|
|
887
|
+
jsonRes(404, {
|
|
888
|
+
ok: false,
|
|
889
|
+
message: `server "${id}" not found`
|
|
890
|
+
});
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
const row = listServers().find((r) => r.id === id);
|
|
894
|
+
if (row !== void 0) {
|
|
895
|
+
if (parsed.disabled) connectingSince.delete(row.config.serverName);
|
|
896
|
+
else markConnecting(row.config.serverName);
|
|
897
|
+
}
|
|
898
|
+
jsonRes(200, {
|
|
899
|
+
ok: true,
|
|
900
|
+
id,
|
|
901
|
+
disabled: parsed.disabled,
|
|
902
|
+
message: `server ${id} ${parsed.disabled ? "disabled" : "enabled"} (config preserved)`
|
|
903
|
+
});
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
772
906
|
if (method === "DELETE" && putMatch !== null) {
|
|
773
907
|
const id = decodeURIComponent(putMatch[1]);
|
|
774
908
|
if (!deleteServer(id, { loadOverlayPatches })) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huanlin/dsh-plugin-mcp-manager",
|
|
3
3
|
"description": "MCP 服务器管理面板:GUI 增删改 MCP 服务器配置(写入 profile cordis.patch.yml 的 mcp-client insert 行,配置 HMR 实时挂载)+ 工具浏览 + agent 面 mcp_* 管理工具。连接生命周期委托官方 @deepseek-ai/dsh-mcp-client。",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|