@rezti/dsh-rez-suite 0.1.6 → 0.1.7
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 +1 -1
- package/README.zh.md +1 -1
- package/lib/client.d.ts +1 -0
- package/lib/client.js +6 -0
- package/lib/index.js +102 -28
- package/package.json +5 -5
- package/src/client/locales.ts +2 -0
- package/src/client/panel/StatusTab.tsx +1 -0
- package/src/index.ts +17 -23
- package/src/observe-mcp.ts +121 -0
package/README.md
CHANGED
|
@@ -8,4 +8,4 @@ dsh plugin --profile web add @rezti/dsh-rez-suite
|
|
|
8
8
|
|
|
9
9
|
组成见仓库根 README。子包不要单独 `dsh plugin add`。
|
|
10
10
|
|
|
11
|
-
配置在 **设置 → 插件 → 插件配置** 展开 **ReZ-TI**(同页还有独立的 ReZ-TI 标签)。Odoo / Nextcloud / WeCom / HA
|
|
11
|
+
配置在 **设置 → 插件 → 插件配置** 展开 **ReZ-TI**(同页还有独立的 ReZ-TI 标签)。Odoo / Nextcloud / WeCom / HA 的地址和密钥都在「配置」页。个人微信在卡片的「微信」页扫码,然后直接在微信里说话。状态页看的是本机已注册的 `mcp__odoo__*` 等工具,不是直接 ping 远端 MCP。
|
package/README.zh.md
CHANGED
|
@@ -8,6 +8,6 @@ dsh plugin --profile web add @rezti/dsh-rez-suite
|
|
|
8
8
|
|
|
9
9
|
密钥用环境变量或 dsh-credentials:`REZ_ODOO_TOKEN`、`REZ_NEXTCLOUD_PASSWORD`、`REZ_WECHAT_WEBHOOK`、`REZ_HA_TOKEN`。未配置时不拉起对应 MCP。
|
|
10
10
|
|
|
11
|
-
配置在 **设置 → 插件 → 插件配置** 展开 **ReZ-TI**(同页还有独立的 ReZ-TI 标签)。Odoo / Nextcloud / 企微 / HA
|
|
11
|
+
配置在 **设置 → 插件 → 插件配置** 展开 **ReZ-TI**(同页还有独立的 ReZ-TI 标签)。Odoo / Nextcloud / 企微 / HA 的地址和密钥都在「配置」页。个人微信在卡片的「微信」页扫码,然后直接在微信里说话。状态页看的是本机已注册的 `mcp__odoo__*` 等工具,不是直接 ping 远端 MCP。
|
|
12
12
|
|
|
13
13
|
子包不要再单独 `dsh plugin add`,否则会重复注册 MCP。
|
package/lib/client.d.ts
CHANGED
|
@@ -7307,6 +7307,7 @@ declare const zh: {
|
|
|
7307
7307
|
readonly 'config.testResult': "{server}: {status}";
|
|
7308
7308
|
readonly 'config.testOk': "成功({tools} 个工具, {latency}ms)";
|
|
7309
7309
|
readonly 'config.testFail': "失败:{error}";
|
|
7310
|
+
readonly 'status.hint': "绿标表示本机已注册 mcp__<系统>__* 工具。远端 MCP 通了但这里仍红,说明 dsh-mcp-client 还没挂上。";
|
|
7310
7311
|
readonly 'status.total': "当前角色 {role},共注册 {count} 个 MCP 工具";
|
|
7311
7312
|
readonly 'status.col.server': "Server";
|
|
7312
7313
|
readonly 'status.col.state': "状态";
|
package/lib/client.js
CHANGED
|
@@ -68,6 +68,7 @@ const zh = {
|
|
|
68
68
|
"config.testResult": "{server}: {status}",
|
|
69
69
|
"config.testOk": "成功({tools} 个工具, {latency}ms)",
|
|
70
70
|
"config.testFail": "失败:{error}",
|
|
71
|
+
"status.hint": "绿标表示本机已注册 mcp__<系统>__* 工具。远端 MCP 通了但这里仍红,说明 dsh-mcp-client 还没挂上。",
|
|
71
72
|
"status.total": "当前角色 {role},共注册 {count} 个 MCP 工具",
|
|
72
73
|
"status.col.server": "Server",
|
|
73
74
|
"status.col.state": "状态",
|
|
@@ -164,6 +165,7 @@ const en = {
|
|
|
164
165
|
"config.testResult": "{server}: {status}",
|
|
165
166
|
"config.testOk": "OK ({tools} tools, {latency}ms)",
|
|
166
167
|
"config.testFail": "Failed: {error}",
|
|
168
|
+
"status.hint": "Green means this host registered mcp__<system>__* tools. A live remote MCP with a red row means dsh-mcp-client has not attached yet.",
|
|
167
169
|
"status.total": "Role {role}, {count} MCP tools registered",
|
|
168
170
|
"status.col.server": "Server",
|
|
169
171
|
"status.col.state": "State",
|
|
@@ -977,6 +979,10 @@ function StatusTab({ api }) {
|
|
|
977
979
|
children: tt("common.refresh")
|
|
978
980
|
})]
|
|
979
981
|
}),
|
|
982
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
983
|
+
className: panel_module_default.message,
|
|
984
|
+
children: tt("status.hint")
|
|
985
|
+
}),
|
|
980
986
|
error !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
981
987
|
className: panel_module_default.error,
|
|
982
988
|
children: error
|
package/lib/index.js
CHANGED
|
@@ -2842,6 +2842,98 @@ defineMethod("transform", [
|
|
|
2842
2842
|
"preserve"
|
|
2843
2843
|
], ({ inner }, isInner) => inner.toString(isInner));
|
|
2844
2844
|
//#endregion
|
|
2845
|
+
//#region src/observe-mcp.ts
|
|
2846
|
+
/**
|
|
2847
|
+
* Live MCP status without a second host.
|
|
2848
|
+
*
|
|
2849
|
+
* Official @deepseek-ai/dsh-mcp-client registers tools as mcp__<server>__*.
|
|
2850
|
+
* The settings Status tab used to hard-code disconnected after MCP ownership
|
|
2851
|
+
* moved; this module reads the same registry the agent sees.
|
|
2852
|
+
*/
|
|
2853
|
+
const COMPOSED_SERVERS = [
|
|
2854
|
+
"odoo",
|
|
2855
|
+
"nextcloud",
|
|
2856
|
+
"wechat",
|
|
2857
|
+
"homeassistant"
|
|
2858
|
+
];
|
|
2859
|
+
function mcpToolPrefix(server) {
|
|
2860
|
+
return "mcp__" + server + "__";
|
|
2861
|
+
}
|
|
2862
|
+
/** Count tools whose public name belongs to one composed MCP server. */
|
|
2863
|
+
function countMcpTools(toolNames, server) {
|
|
2864
|
+
const prefix = mcpToolPrefix(server);
|
|
2865
|
+
let count = 0;
|
|
2866
|
+
for (const name of toolNames) if (name.startsWith(prefix)) count += 1;
|
|
2867
|
+
return count;
|
|
2868
|
+
}
|
|
2869
|
+
function listRegisteredToolNames(tools) {
|
|
2870
|
+
if (tools === void 0 || typeof tools.schemas !== "function") return [];
|
|
2871
|
+
try {
|
|
2872
|
+
return tools.schemas().map((schema) => schema.name);
|
|
2873
|
+
} catch {
|
|
2874
|
+
return [];
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
function isEnabled(config, server) {
|
|
2878
|
+
return config.connections[server].enabled !== false;
|
|
2879
|
+
}
|
|
2880
|
+
function observeServer(server, input) {
|
|
2881
|
+
const enabled = isEnabled(input.config, server);
|
|
2882
|
+
const secret = (input.secretPresent ?? secretConfigured)(server);
|
|
2883
|
+
const toolCount = countMcpTools(input.toolNames, server);
|
|
2884
|
+
const ref = REZ_CREDENTIAL_REFS[server];
|
|
2885
|
+
if (!enabled) return {
|
|
2886
|
+
server,
|
|
2887
|
+
enabled: false,
|
|
2888
|
+
state: "disconnected",
|
|
2889
|
+
toolCount: 0,
|
|
2890
|
+
registeredTools: 0,
|
|
2891
|
+
lastError: "已在设置中关闭"
|
|
2892
|
+
};
|
|
2893
|
+
if (toolCount > 0) return {
|
|
2894
|
+
server,
|
|
2895
|
+
enabled: true,
|
|
2896
|
+
state: "connected",
|
|
2897
|
+
toolCount,
|
|
2898
|
+
registeredTools: toolCount
|
|
2899
|
+
};
|
|
2900
|
+
if (!secret) return {
|
|
2901
|
+
server,
|
|
2902
|
+
enabled: true,
|
|
2903
|
+
state: "disconnected",
|
|
2904
|
+
toolCount: 0,
|
|
2905
|
+
registeredTools: 0,
|
|
2906
|
+
lastError: "未配置 " + ref
|
|
2907
|
+
};
|
|
2908
|
+
return {
|
|
2909
|
+
server,
|
|
2910
|
+
enabled: true,
|
|
2911
|
+
state: "disconnected",
|
|
2912
|
+
toolCount: 0,
|
|
2913
|
+
registeredTools: 0,
|
|
2914
|
+
lastError: ref + " 已配置,但本机尚未注册 " + mcpToolPrefix(server) + "*(dsh-mcp-client 未挂上)"
|
|
2915
|
+
};
|
|
2916
|
+
}
|
|
2917
|
+
function observeComposedServers(input) {
|
|
2918
|
+
return COMPOSED_SERVERS.map((server) => observeServer(server, input));
|
|
2919
|
+
}
|
|
2920
|
+
function testComposedServers(input) {
|
|
2921
|
+
return observeComposedServers(input).map((row) => {
|
|
2922
|
+
if (row.state === "connected") return {
|
|
2923
|
+
server: row.server,
|
|
2924
|
+
ok: true,
|
|
2925
|
+
toolCount: row.toolCount,
|
|
2926
|
+
serverInfo: row.toolCount + " tools registered (" + mcpToolPrefix(row.server) + "*)"
|
|
2927
|
+
};
|
|
2928
|
+
return {
|
|
2929
|
+
server: row.server,
|
|
2930
|
+
ok: false,
|
|
2931
|
+
toolCount: 0,
|
|
2932
|
+
error: row.lastError ?? "disconnected"
|
|
2933
|
+
};
|
|
2934
|
+
});
|
|
2935
|
+
}
|
|
2936
|
+
//#endregion
|
|
2845
2937
|
//#region src/protocol.ts
|
|
2846
2938
|
const REZ_API = {
|
|
2847
2939
|
config: "/api/dsh-rez-suite/config",
|
|
@@ -7617,12 +7709,6 @@ const Config = Schema.object({
|
|
|
7617
7709
|
})
|
|
7618
7710
|
});
|
|
7619
7711
|
const SECTION_ORDER = 150;
|
|
7620
|
-
const COMPOSED_SERVERS = [
|
|
7621
|
-
"odoo",
|
|
7622
|
-
"nextcloud",
|
|
7623
|
-
"wechat",
|
|
7624
|
-
"homeassistant"
|
|
7625
|
-
];
|
|
7626
7712
|
const REZ_GUIDANCE = [
|
|
7627
7713
|
"本机已安装 ReZ-TI 套件(一次安装):工作身份由 dsh-rez-sso 提供,MCP 由官方 dsh-mcp-client 接入 Odoo / Nextcloud / 企业微信 / Home Assistant。",
|
|
7628
7714
|
"工具名 mcp__odoo__*、mcp__nextcloud__*、mcp__wechat__*、mcp__homeassistant__*。",
|
|
@@ -7634,29 +7720,17 @@ const REZ_GUIDANCE = [
|
|
|
7634
7720
|
function normalize(input) {
|
|
7635
7721
|
return mergeConfig(defaultConfig(), input ?? {});
|
|
7636
7722
|
}
|
|
7637
|
-
function delegatedHost() {
|
|
7638
|
-
const
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
state: "disconnected",
|
|
7642
|
-
toolCount: 0,
|
|
7643
|
-
registeredTools: 0,
|
|
7644
|
-
lastError: secretConfigured(server) ? "owned by @deepseek-ai/dsh-mcp-client" : "missing " + REZ_CREDENTIAL_REFS[server]
|
|
7645
|
-
}));
|
|
7646
|
-
const test = async () => COMPOSED_SERVERS.map((server) => {
|
|
7647
|
-
return secretConfigured(server) ? {
|
|
7648
|
-
server,
|
|
7649
|
-
ok: true,
|
|
7650
|
-
serverInfo: REZ_CREDENTIAL_REFS[server] + " configured"
|
|
7651
|
-
} : {
|
|
7652
|
-
server,
|
|
7653
|
-
ok: false,
|
|
7654
|
-
error: "missing " + REZ_CREDENTIAL_REFS[server]
|
|
7655
|
-
};
|
|
7723
|
+
function delegatedHost(ctx, getConfig) {
|
|
7724
|
+
const snapshot = () => observeComposedServers({
|
|
7725
|
+
config: getConfig(),
|
|
7726
|
+
toolNames: listRegisteredToolNames(ctx.tools)
|
|
7656
7727
|
});
|
|
7657
7728
|
return {
|
|
7658
|
-
status,
|
|
7659
|
-
test
|
|
7729
|
+
status: () => snapshot(),
|
|
7730
|
+
test: async () => testComposedServers({
|
|
7731
|
+
config: getConfig(),
|
|
7732
|
+
toolNames: listRegisteredToolNames(ctx.tools)
|
|
7733
|
+
}),
|
|
7660
7734
|
sync: async () => void 0,
|
|
7661
7735
|
dispose: async () => void 0
|
|
7662
7736
|
};
|
|
@@ -7664,7 +7738,7 @@ function delegatedHost() {
|
|
|
7664
7738
|
function apply(ctx, config) {
|
|
7665
7739
|
let current = () => normalize(config);
|
|
7666
7740
|
const tokens = new TokenManager(joinTokenDb());
|
|
7667
|
-
const host = delegatedHost();
|
|
7741
|
+
const host = delegatedHost(ctx, () => current());
|
|
7668
7742
|
ctx.effect(() => () => {
|
|
7669
7743
|
tokens.close();
|
|
7670
7744
|
}, "dsh-rez-suite: ledger view");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rezti/dsh-rez-suite",
|
|
3
3
|
"description": "ReZ-TI employee plugin for DeepSeek Harness: one install enables company MCP (Odoo / Nextcloud / WeCom / Weixin-ClawBot / Home Assistant) and work identity.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.7",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
40
40
|
"zod": "^4.4.3",
|
|
41
|
+
"@rezti/dsh-rez-ha-bridge": "0.1.5",
|
|
42
|
+
"@rezti/dsh-rez-intent": "0.1.3",
|
|
41
43
|
"@rezti/dsh-rez-nextcloud": "0.1.5",
|
|
42
|
-
"@rezti/dsh-rez-odoo": "0.1.5",
|
|
43
44
|
"@rezti/dsh-rez-sso": "0.1.5",
|
|
44
|
-
"@rezti/dsh-rez-
|
|
45
|
-
"@rezti/dsh-rez-wechat": "0.1.5",
|
|
45
|
+
"@rezti/dsh-rez-odoo": "0.1.5",
|
|
46
46
|
"@rezti/dsh-rez-token-manager": "0.1.3",
|
|
47
|
-
"@rezti/dsh-rez-
|
|
47
|
+
"@rezti/dsh-rez-wechat": "0.1.5"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"react": "^18.2.0",
|
package/src/client/locales.ts
CHANGED
|
@@ -60,6 +60,7 @@ export const zh = {
|
|
|
60
60
|
'config.testOk': '成功({tools} 个工具, {latency}ms)',
|
|
61
61
|
'config.testFail': '失败:{error}',
|
|
62
62
|
// status
|
|
63
|
+
'status.hint': '绿标表示本机已注册 mcp__<系统>__* 工具。远端 MCP 通了但这里仍红,说明 dsh-mcp-client 还没挂上。',
|
|
63
64
|
'status.total': '当前角色 {role},共注册 {count} 个 MCP 工具',
|
|
64
65
|
'status.col.server': 'Server',
|
|
65
66
|
'status.col.state': '状态',
|
|
@@ -161,6 +162,7 @@ export const en: Record<RezKey, string> = {
|
|
|
161
162
|
'config.testResult': '{server}: {status}',
|
|
162
163
|
'config.testOk': 'OK ({tools} tools, {latency}ms)',
|
|
163
164
|
'config.testFail': 'Failed: {error}',
|
|
165
|
+
'status.hint': 'Green means this host registered mcp__<system>__* tools. A live remote MCP with a red row means dsh-mcp-client has not attached yet.',
|
|
164
166
|
'status.total': 'Role {role}, {count} MCP tools registered',
|
|
165
167
|
'status.col.server': 'Server',
|
|
166
168
|
'status.col.state': 'State',
|
|
@@ -36,6 +36,7 @@ export function StatusTab({ api }: { api: RezApi }) {
|
|
|
36
36
|
<span className={css.message}>{tt('status.total', { role: status.role, count: status.totalRegisteredTools })}</span>
|
|
37
37
|
<button type="button" className={css.button} onClick={() => { void load() }}>{tt('common.refresh')}</button>
|
|
38
38
|
</div>
|
|
39
|
+
<p className={css.message}>{tt('status.hint')}</p>
|
|
39
40
|
{error !== '' && <div className={css.error}>{error}</div>}
|
|
40
41
|
<div className={css.tableWrap}>
|
|
41
42
|
<table className={css.table}>
|
package/src/index.ts
CHANGED
|
@@ -15,13 +15,18 @@ import z from '@deepseek-ai/schemastery'
|
|
|
15
15
|
import type {} from '@deepseek-ai/dsh-host-webserver'
|
|
16
16
|
import type {} from '@deepseek-ai/dsh-system-prompt'
|
|
17
17
|
import type {} from '@deepseek-ai/dsh-tools'
|
|
18
|
-
import {
|
|
18
|
+
import { REZ_DEFAULT_CONNECTIONS } from '@rezti/dsh-rez-sso'
|
|
19
19
|
import type { McpHost } from './mcp-host.ts'
|
|
20
|
+
import {
|
|
21
|
+
listRegisteredToolNames,
|
|
22
|
+
observeComposedServers,
|
|
23
|
+
testComposedServers,
|
|
24
|
+
} from './observe-mcp.ts'
|
|
20
25
|
import { makeRoutes } from './routes.ts'
|
|
21
26
|
import { defaultConfig, mergeConfig, saveConfig } from './store.ts'
|
|
22
27
|
import { TokenManager } from './token-manager.ts'
|
|
23
28
|
import { rezConfigTool, rezStatusTool } from './tools.ts'
|
|
24
|
-
import type { RezConfig
|
|
29
|
+
import type { RezConfig } from './protocol.ts'
|
|
25
30
|
|
|
26
31
|
export const name = 'rez-suite'
|
|
27
32
|
|
|
@@ -73,8 +78,6 @@ export const Config: z<RezConfig> = z.object({
|
|
|
73
78
|
|
|
74
79
|
const SECTION_ORDER = 150
|
|
75
80
|
|
|
76
|
-
const COMPOSED_SERVERS = ['odoo', 'nextcloud', 'wechat', 'homeassistant'] as const
|
|
77
|
-
|
|
78
81
|
export const REZ_GUIDANCE = [
|
|
79
82
|
'本机已安装 ReZ-TI 套件(一次安装):工作身份由 dsh-rez-sso 提供,MCP 由官方 dsh-mcp-client 接入 Odoo / Nextcloud / 企业微信 / Home Assistant。',
|
|
80
83
|
'工具名 mcp__odoo__*、mcp__nextcloud__*、mcp__wechat__*、mcp__homeassistant__*。',
|
|
@@ -88,26 +91,17 @@ function normalize(input: Partial<RezConfig> | undefined): RezConfig {
|
|
|
88
91
|
return mergeConfig(defaultConfig(), input ?? {})
|
|
89
92
|
}
|
|
90
93
|
|
|
91
|
-
function delegatedHost(): McpHost {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
state: 'disconnected' as const,
|
|
96
|
-
toolCount: 0,
|
|
97
|
-
registeredTools: 0,
|
|
98
|
-
lastError: secretConfigured(server)
|
|
99
|
-
? 'owned by @deepseek-ai/dsh-mcp-client'
|
|
100
|
-
: 'missing ' + REZ_CREDENTIAL_REFS[server],
|
|
101
|
-
}))
|
|
102
|
-
const test = async (): Promise<RezTestResult[]> => COMPOSED_SERVERS.map(server => {
|
|
103
|
-
const ok = secretConfigured(server)
|
|
104
|
-
return ok
|
|
105
|
-
? { server, ok: true, serverInfo: REZ_CREDENTIAL_REFS[server] + ' configured' }
|
|
106
|
-
: { server, ok: false, error: 'missing ' + REZ_CREDENTIAL_REFS[server] }
|
|
94
|
+
function delegatedHost(ctx: Context, getConfig: () => RezConfig): McpHost {
|
|
95
|
+
const snapshot = () => observeComposedServers({
|
|
96
|
+
config: getConfig(),
|
|
97
|
+
toolNames: listRegisteredToolNames(ctx.tools),
|
|
107
98
|
})
|
|
108
99
|
return {
|
|
109
|
-
status,
|
|
110
|
-
test
|
|
100
|
+
status: () => snapshot(),
|
|
101
|
+
test: async () => testComposedServers({
|
|
102
|
+
config: getConfig(),
|
|
103
|
+
toolNames: listRegisteredToolNames(ctx.tools),
|
|
104
|
+
}),
|
|
111
105
|
sync: async () => undefined,
|
|
112
106
|
dispose: async () => undefined,
|
|
113
107
|
} as unknown as McpHost
|
|
@@ -117,7 +111,7 @@ export function apply(ctx: Context, config?: RezConfig): void {
|
|
|
117
111
|
let current: () => RezConfig = () => normalize(config)
|
|
118
112
|
|
|
119
113
|
const tokens = new TokenManager(joinTokenDb())
|
|
120
|
-
const host = delegatedHost()
|
|
114
|
+
const host = delegatedHost(ctx, () => current())
|
|
121
115
|
ctx.effect(() => () => { tokens.close() }, 'dsh-rez-suite: ledger view')
|
|
122
116
|
|
|
123
117
|
const tools = [
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live MCP status without a second host.
|
|
3
|
+
*
|
|
4
|
+
* Official @deepseek-ai/dsh-mcp-client registers tools as mcp__<server>__*.
|
|
5
|
+
* The settings Status tab used to hard-code disconnected after MCP ownership
|
|
6
|
+
* moved; this module reads the same registry the agent sees.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { REZ_CREDENTIAL_REFS, secretConfigured, type RezSystem } from '@rezti/dsh-rez-sso'
|
|
10
|
+
import type { RezConfig, RezServerStatus, RezTestResult } from './protocol.ts'
|
|
11
|
+
|
|
12
|
+
export const COMPOSED_SERVERS = ['odoo', 'nextcloud', 'wechat', 'homeassistant'] as const
|
|
13
|
+
|
|
14
|
+
export type ComposedServer = (typeof COMPOSED_SERVERS)[number]
|
|
15
|
+
|
|
16
|
+
export function mcpToolPrefix(server: string): string {
|
|
17
|
+
return 'mcp__' + server + '__'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Count tools whose public name belongs to one composed MCP server. */
|
|
21
|
+
export function countMcpTools(toolNames: readonly string[], server: string): number {
|
|
22
|
+
const prefix = mcpToolPrefix(server)
|
|
23
|
+
let count = 0
|
|
24
|
+
for (const name of toolNames) {
|
|
25
|
+
if (name.startsWith(prefix)) count += 1
|
|
26
|
+
}
|
|
27
|
+
return count
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function listRegisteredToolNames(tools: { schemas?: () => ReadonlyArray<{ name: string }> } | undefined): string[] {
|
|
31
|
+
if (tools === undefined || typeof tools.schemas !== 'function') return []
|
|
32
|
+
try {
|
|
33
|
+
return tools.schemas().map(schema => schema.name)
|
|
34
|
+
} catch {
|
|
35
|
+
return []
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ObserveInput {
|
|
40
|
+
config: RezConfig
|
|
41
|
+
toolNames: readonly string[]
|
|
42
|
+
secretPresent?: (server: RezSystem) => boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isEnabled(config: RezConfig, server: ComposedServer): boolean {
|
|
46
|
+
return config.connections[server].enabled !== false
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function observeServer(
|
|
50
|
+
server: ComposedServer,
|
|
51
|
+
input: ObserveInput,
|
|
52
|
+
): RezServerStatus {
|
|
53
|
+
const enabled = isEnabled(input.config, server)
|
|
54
|
+
const secret = (input.secretPresent ?? secretConfigured)(server)
|
|
55
|
+
const toolCount = countMcpTools(input.toolNames, server)
|
|
56
|
+
const ref = REZ_CREDENTIAL_REFS[server]
|
|
57
|
+
|
|
58
|
+
if (!enabled) {
|
|
59
|
+
return {
|
|
60
|
+
server,
|
|
61
|
+
enabled: false,
|
|
62
|
+
state: 'disconnected',
|
|
63
|
+
toolCount: 0,
|
|
64
|
+
registeredTools: 0,
|
|
65
|
+
lastError: '已在设置中关闭',
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (toolCount > 0) {
|
|
70
|
+
return {
|
|
71
|
+
server,
|
|
72
|
+
enabled: true,
|
|
73
|
+
state: 'connected',
|
|
74
|
+
toolCount,
|
|
75
|
+
registeredTools: toolCount,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!secret) {
|
|
80
|
+
return {
|
|
81
|
+
server,
|
|
82
|
+
enabled: true,
|
|
83
|
+
state: 'disconnected',
|
|
84
|
+
toolCount: 0,
|
|
85
|
+
registeredTools: 0,
|
|
86
|
+
lastError: '未配置 ' + ref,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
server,
|
|
92
|
+
enabled: true,
|
|
93
|
+
state: 'disconnected',
|
|
94
|
+
toolCount: 0,
|
|
95
|
+
registeredTools: 0,
|
|
96
|
+
lastError: ref + ' 已配置,但本机尚未注册 ' + mcpToolPrefix(server) + '*(dsh-mcp-client 未挂上)',
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function observeComposedServers(input: ObserveInput): RezServerStatus[] {
|
|
101
|
+
return COMPOSED_SERVERS.map(server => observeServer(server, input))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function testComposedServers(input: ObserveInput): RezTestResult[] {
|
|
105
|
+
return observeComposedServers(input).map(row => {
|
|
106
|
+
if (row.state === 'connected') {
|
|
107
|
+
return {
|
|
108
|
+
server: row.server,
|
|
109
|
+
ok: true,
|
|
110
|
+
toolCount: row.toolCount,
|
|
111
|
+
serverInfo: row.toolCount + ' tools registered (' + mcpToolPrefix(row.server) + '*)',
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
server: row.server,
|
|
116
|
+
ok: false,
|
|
117
|
+
toolCount: 0,
|
|
118
|
+
error: row.lastError ?? 'disconnected',
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
}
|