dsh-mcp 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.en.md +30 -0
- package/CHANGELOG.md +30 -0
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/lib/client.js +234 -152
- package/lib/index.js +38 -7
- package/lib/mcp-client.js +40 -5
- package/lib/oauth.js +182 -13
- package/package.json +3 -2
- package/src/client/ServerForm.tsx +9 -1
- package/src/client/ServersJsonEditor.tsx +56 -42
- package/src/client/locales.ts +10 -4
- package/src/client/mcp-store.ts +62 -1
package/CHANGELOG.en.md
CHANGED
|
@@ -7,6 +7,36 @@ All notable changes to this project are documented in this file.
|
|
|
7
7
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
8
8
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
9
9
|
|
|
10
|
+
## [Unreleased]
|
|
11
|
+
|
|
12
|
+
## [1.8.0] - 2026-08-27
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **OAuth authorization UX**: mounts no longer open the browser (they fail with guidance when authorization is needed; only Test connection auto-opens the browser); tool calls with a missing/expired token return a **clickable authorization link** (a background callback listener on the stable port completes the flow; concurrent calls reuse the same pending flow); a global authorization queue keeps at most one flow active at a time
|
|
17
|
+
- **Mount failure now carries an authorization link**: when a mount needs OAuth authorization, the failure message includes a **clickable authorization link**; once the user opens it, the tokens are stored automatically — no manual trip to the settings page
|
|
18
|
+
- **New `allowBrowserOnMount` config**: `false` by default (mounts never pop the browser); set it to `true` to restore the legacy behavior (mounts open the browser for authorization). Configure it under the dsh-mcp entry in the profile's `cordis.patch.yml`
|
|
19
|
+
- **stdio form argument input**: arguments now parse shell-style (space/newline separated, with quote, escape, and explicit empty-argument support) so a command line pastes directly; the args box shows a live parsed-argument preview so split mistakes surface before saving
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **OAuth authorization link was dropped from the mount failure message**: the underlying connect error (including the authorization link) used to be folded into `cause`, while the mount failure view only showed the message; the detail is now folded into the message
|
|
24
|
+
- **Tools did not register automatically after OAuth authorization**: saving tokens now remounts every enabled server of that name by serverName, so tools appear without a manual refresh or restart (the listener previously subscribed to a credential event name the service never dispatches, so the remount never ran; fixed)
|
|
25
|
+
- **Callback-port conflict crashed the Host**: all authorization flows now share one deduplicated per-server loopback listener and every listen has an error handler — an `EADDRINUSE` can no longer crash the DSH process
|
|
26
|
+
- **Wrong `resource` parameter in the authorization link**: the link used to serialize `resource=undefined`, which the authorization server rejects; it now passes a URL object (preferring the protected-resource metadata's resource)
|
|
27
|
+
- **stdio working-directory pitfall**: the form now explains that an empty cwd inherits the Host working directory and that a pnpm workspace there can make npx and similar commands resolve the wrong local package; the hint carries no concrete path, leaving the value to the user
|
|
28
|
+
|
|
29
|
+
## [1.7.0] - 2026-08-21
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- **JSON config editor switched to key-value format**: server configs are shown/edited as a JSON object keyed by server name (`{ "server": { "type": "streamable_http", "url": ..., "headers": {...}, "disabled": false } }`) instead of the previous array; `type` is `streamable_http` or `stdio`, `disabled: true` disables the server
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- **MCP image admission diagnostics no longer misreport failures**: distinguish image count, batch/per-image byte, MIME, Base64, raster format, decoded-pixel, and maximum-dimension limits; unknown admission errors use a fixed diagnostic so valid-but-oversized images are not reported as invalid image data and attachment storage internals are not leaked (PR #5, thanks @coding-chong)
|
|
38
|
+
- The publish workflow now runs `npm test` (image-projection regression) before syntax checks and publishing
|
|
39
|
+
|
|
10
40
|
## [1.6.0] - 2026-08-17
|
|
11
41
|
|
|
12
42
|
### Added
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,36 @@
|
|
|
7
7
|
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
|
|
8
8
|
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
9
9
|
|
|
10
|
+
## [Unreleased]
|
|
11
|
+
|
|
12
|
+
## [1.8.0] - 2026-08-27
|
|
13
|
+
|
|
14
|
+
### 新增
|
|
15
|
+
|
|
16
|
+
- **OAuth 授权交互改进**:启动挂载不再自动弹浏览器(需授权时失败并提示,仅测试连接自动弹浏览器);工具调用发现 token 缺失/过期时返回**可点击的授权链接**(后台回调监听器在稳定端口完成授权,并发请求复用同一流程);全局授权队列保证同一时间最多一个授权流程
|
|
17
|
+
- **挂载失败直接附授权链接**:挂载需要授权时,失败信息直接附带**可点击的授权链接**,打开链接完成授权后 token 自动保存,无需再手动去设置页操作
|
|
18
|
+
- **新增 `allowBrowserOnMount` 配置**:默认 `false`(挂载不自动弹浏览器);设为 `true` 可恢复旧行为(挂载时自动打开浏览器授权)。在 profile 的 `cordis.patch.yml` 中 dsh-mcp 条目下配置
|
|
19
|
+
- **stdio 表单参数输入改进**:参数支持 shell 风格解析(空格/换行分隔,支持引号、转义与显式空参数),命令行可直接粘贴即用;参数框下方实时预览解析结果,保存前即可发现参数拆分问题
|
|
20
|
+
|
|
21
|
+
### 修复
|
|
22
|
+
|
|
23
|
+
- **OAuth 授权链接未在挂载失败信息中展示**:底层连接错误(含授权链接)此前被塞进 `cause`,挂载失败视图只显示通用文案;现已将详情并入 message
|
|
24
|
+
- **OAuth 授权完成后工具不自动注册**:授权保存 token 后按 serverName 自动重挂载对应服务器,工具立即注册,无需手动刷新或重启(此前监听的事件名与凭据服务实际发出的事件不一致,自动重挂载一直未生效,现已修正)
|
|
25
|
+
- **授权回调端口冲突导致进程崩溃**:统一按服务器共享回调监听器(同服务器去重),并为所有 listen 增加错误处理——`EADDRINUSE` 不再导致 DSH 进程崩溃
|
|
26
|
+
- **授权链接 resource 参数错误**:链接中 `resource=undefined` 会导致授权服务器校验失败;改为传 URL 对象(优先使用 protected-resource 元数据的 resource)
|
|
27
|
+
- **stdio 工作目录留空时易踩坑**:表单为 cwd 补充说明——留空会继承 Host 工作目录,若该目录是 pnpm workspace,npx 等命令可能解析到错误的本地包;提示不含具体路径,由使用者自行填写
|
|
28
|
+
|
|
29
|
+
## [1.7.0] - 2026-08-21
|
|
30
|
+
|
|
31
|
+
### 新增
|
|
32
|
+
|
|
33
|
+
- **JSON 配置编辑器改为 key-value 格式**:服务器配置以「服务器名 → 配置对象」的 JSON 对象展示/编辑(`{ "服务器名": { "type": "streamable_http", "url": ..., "headers": {...}, "disabled": false } }`),替代原数组格式;`type` 取值 `streamable_http`/`stdio`,`disabled: true` 表示停用
|
|
34
|
+
|
|
35
|
+
### 修复
|
|
36
|
+
|
|
37
|
+
- **MCP 图片 admission 诊断不再误报**:区分图片数量、批量/单图字节数、MIME 类型、Base64、图片格式、解码像素数和最大边长限制;未知 admission 错误使用固定诊断,避免把有效但超尺寸的图片误报为无效图片数据,也不泄露 attachment 存储内部错误(PR #5,感谢 @coding-chong)
|
|
38
|
+
- 发布流水线增加 `npm test`(图片投影回归测试)后再校验语法并发布
|
|
39
|
+
|
|
10
40
|
## [1.6.0] - 2026-08-17
|
|
11
41
|
|
|
12
42
|
### 新增
|
package/README.en.md
CHANGED
|
@@ -153,7 +153,7 @@ Check in order:
|
|
|
153
153
|
the `web` profile (`dsh plugin --profile web add dsh-mcp` +
|
|
154
154
|
`$DSH_HOME/profiles/web/cordis.patch.yml`); other profiles have their own settings pages.
|
|
155
155
|
5. **Is it the latest version?** npm metadata caching can pin an old version; force the version
|
|
156
|
-
with `dsh plugin --profile web add dsh-mcp@latest` (or `@1.
|
|
156
|
+
with `dsh plugin --profile web add dsh-mcp@latest` (or `@1.8.0`).
|
|
157
157
|
|
|
158
158
|
**Q2: "MCP" is visible but the server list is empty or errors?**
|
|
159
159
|
|
package/README.md
CHANGED
|
@@ -136,7 +136,7 @@ dsh plugin --profile web add link:<本仓库绝对路径>
|
|
|
136
136
|
(`dsh plugin --profile web add dsh-mcp` + `$DSH_HOME/profiles/web/cordis.patch.yml`);
|
|
137
137
|
装到其他 profile 则在其他 profile 的设置页查看。
|
|
138
138
|
5. **是否为最新版本**:npm 元数据缓存可能导致装到旧版,可强制指定版本
|
|
139
|
-
`dsh plugin --profile web add dsh-mcp@latest`(或 `@1.
|
|
139
|
+
`dsh plugin --profile web add dsh-mcp@latest`(或 `@1.8.0`)。
|
|
140
140
|
|
|
141
141
|
**Q2:设置页能看到「MCP」,但服务器列表为空/报错?**
|
|
142
142
|
|
package/lib/client.js
CHANGED
|
@@ -15041,6 +15041,175 @@ var import_react4 = require("react");
|
|
|
15041
15041
|
// src/client/ServerForm.tsx
|
|
15042
15042
|
var import_react = require("react");
|
|
15043
15043
|
|
|
15044
|
+
// src/client/mcp-store.ts
|
|
15045
|
+
var import_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
15046
|
+
var DEFAULT_TOOL_CALL_TIMEOUT_MS = 6e4;
|
|
15047
|
+
function emptyDraft() {
|
|
15048
|
+
return {
|
|
15049
|
+
id: null,
|
|
15050
|
+
serverName: "",
|
|
15051
|
+
transport: "stdio",
|
|
15052
|
+
enabled: true,
|
|
15053
|
+
command: "",
|
|
15054
|
+
argsText: "",
|
|
15055
|
+
cwd: "",
|
|
15056
|
+
url: "",
|
|
15057
|
+
headersText: "",
|
|
15058
|
+
toolCallTimeoutMs: String(DEFAULT_TOOL_CALL_TIMEOUT_MS),
|
|
15059
|
+
// Reject the mount by default when the startup connection fails, so a
|
|
15060
|
+
// broken server never registers stale tools.
|
|
15061
|
+
failOnStartupError: true
|
|
15062
|
+
};
|
|
15063
|
+
}
|
|
15064
|
+
function draftFromServer(server) {
|
|
15065
|
+
return {
|
|
15066
|
+
id: server.id,
|
|
15067
|
+
serverName: server.serverName,
|
|
15068
|
+
transport: server.transport,
|
|
15069
|
+
enabled: server.enabled,
|
|
15070
|
+
command: server.command,
|
|
15071
|
+
argsText: server.args.join("\n"),
|
|
15072
|
+
cwd: server.cwd,
|
|
15073
|
+
url: server.url,
|
|
15074
|
+
headersText: server.headers.map((header) => `${header.name}: ${header.value}`).join("\n"),
|
|
15075
|
+
toolCallTimeoutMs: String(server.toolCallTimeoutMs),
|
|
15076
|
+
failOnStartupError: server.failOnStartupError
|
|
15077
|
+
};
|
|
15078
|
+
}
|
|
15079
|
+
function parseLines(text) {
|
|
15080
|
+
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
15081
|
+
}
|
|
15082
|
+
function parseArgs(text) {
|
|
15083
|
+
const args = [];
|
|
15084
|
+
let current = "";
|
|
15085
|
+
let quote = null;
|
|
15086
|
+
let hasToken = false;
|
|
15087
|
+
let i = 0;
|
|
15088
|
+
while (i < text.length) {
|
|
15089
|
+
const ch = text[i];
|
|
15090
|
+
if (quote !== null) {
|
|
15091
|
+
if (ch === quote) {
|
|
15092
|
+
quote = null;
|
|
15093
|
+
i += 1;
|
|
15094
|
+
continue;
|
|
15095
|
+
}
|
|
15096
|
+
if (ch === "\\" && quote === '"' && i + 1 < text.length && (text[i + 1] === '"' || text[i + 1] === "\\")) {
|
|
15097
|
+
current += text[i + 1];
|
|
15098
|
+
i += 2;
|
|
15099
|
+
continue;
|
|
15100
|
+
}
|
|
15101
|
+
current += ch;
|
|
15102
|
+
i += 1;
|
|
15103
|
+
continue;
|
|
15104
|
+
}
|
|
15105
|
+
if (ch === '"' || ch === "'") {
|
|
15106
|
+
quote = ch;
|
|
15107
|
+
hasToken = true;
|
|
15108
|
+
i += 1;
|
|
15109
|
+
continue;
|
|
15110
|
+
}
|
|
15111
|
+
if (ch === "\\" && i + 1 < text.length) {
|
|
15112
|
+
current += text[i + 1];
|
|
15113
|
+
hasToken = true;
|
|
15114
|
+
i += 2;
|
|
15115
|
+
continue;
|
|
15116
|
+
}
|
|
15117
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
|
|
15118
|
+
if (hasToken || current.length > 0) {
|
|
15119
|
+
args.push(current);
|
|
15120
|
+
current = "";
|
|
15121
|
+
hasToken = false;
|
|
15122
|
+
}
|
|
15123
|
+
i += 1;
|
|
15124
|
+
continue;
|
|
15125
|
+
}
|
|
15126
|
+
current += ch;
|
|
15127
|
+
hasToken = true;
|
|
15128
|
+
i += 1;
|
|
15129
|
+
}
|
|
15130
|
+
if (quote !== null || hasToken || current.length > 0) {
|
|
15131
|
+
args.push(current);
|
|
15132
|
+
}
|
|
15133
|
+
return args;
|
|
15134
|
+
}
|
|
15135
|
+
function parseHeaders(text) {
|
|
15136
|
+
const headers = [];
|
|
15137
|
+
for (const line of parseLines(text)) {
|
|
15138
|
+
const separator = line.indexOf(":");
|
|
15139
|
+
if (separator <= 0) continue;
|
|
15140
|
+
const name = line.slice(0, separator).trim();
|
|
15141
|
+
const value = line.slice(separator + 1).trim();
|
|
15142
|
+
if (name.length > 0) headers.push({ name, value });
|
|
15143
|
+
}
|
|
15144
|
+
return headers;
|
|
15145
|
+
}
|
|
15146
|
+
function parseTimeout(text) {
|
|
15147
|
+
const value = Number(text);
|
|
15148
|
+
return Number.isInteger(value) && value > 0 ? value : DEFAULT_TOOL_CALL_TIMEOUT_MS;
|
|
15149
|
+
}
|
|
15150
|
+
function draftToSubmission(draft) {
|
|
15151
|
+
return {
|
|
15152
|
+
server: {
|
|
15153
|
+
serverName: draft.serverName.trim(),
|
|
15154
|
+
transport: draft.transport,
|
|
15155
|
+
enabled: draft.enabled,
|
|
15156
|
+
command: draft.command.trim(),
|
|
15157
|
+
args: parseArgs(draft.argsText),
|
|
15158
|
+
cwd: draft.cwd.trim(),
|
|
15159
|
+
url: draft.url.trim(),
|
|
15160
|
+
headers: parseHeaders(draft.headersText),
|
|
15161
|
+
toolCallTimeoutMs: parseTimeout(draft.toolCallTimeoutMs),
|
|
15162
|
+
failOnStartupError: draft.failOnStartupError
|
|
15163
|
+
}
|
|
15164
|
+
};
|
|
15165
|
+
}
|
|
15166
|
+
function createMcpManagerStore() {
|
|
15167
|
+
return (0, import_client.defineStore)({
|
|
15168
|
+
init: () => ({
|
|
15169
|
+
loadState: "loading",
|
|
15170
|
+
servers: [],
|
|
15171
|
+
draft: null,
|
|
15172
|
+
busy: null,
|
|
15173
|
+
testRunning: false,
|
|
15174
|
+
test: null
|
|
15175
|
+
}),
|
|
15176
|
+
actions: {
|
|
15177
|
+
setLoadState: (d, state) => {
|
|
15178
|
+
d.loadState = state;
|
|
15179
|
+
},
|
|
15180
|
+
setServers: (d, servers) => {
|
|
15181
|
+
d.servers = servers;
|
|
15182
|
+
},
|
|
15183
|
+
beginCreate: (d) => {
|
|
15184
|
+
d.draft = emptyDraft();
|
|
15185
|
+
d.test = null;
|
|
15186
|
+
},
|
|
15187
|
+
beginEdit: (d, server) => {
|
|
15188
|
+
d.draft = draftFromServer(server);
|
|
15189
|
+
d.test = null;
|
|
15190
|
+
},
|
|
15191
|
+
cancelEdit: (d) => {
|
|
15192
|
+
d.draft = null;
|
|
15193
|
+
d.test = null;
|
|
15194
|
+
d.busy = null;
|
|
15195
|
+
},
|
|
15196
|
+
updateDraft: (d, patch) => {
|
|
15197
|
+
if (d.draft === null) return;
|
|
15198
|
+
d.draft = { ...d.draft, ...patch };
|
|
15199
|
+
},
|
|
15200
|
+
setBusy: (d, busy) => {
|
|
15201
|
+
d.busy = busy;
|
|
15202
|
+
},
|
|
15203
|
+
setTestRunning: (d, running) => {
|
|
15204
|
+
d.testRunning = running;
|
|
15205
|
+
},
|
|
15206
|
+
setTest: (d, test) => {
|
|
15207
|
+
d.test = test;
|
|
15208
|
+
}
|
|
15209
|
+
}
|
|
15210
|
+
});
|
|
15211
|
+
}
|
|
15212
|
+
|
|
15044
15213
|
// src/client/ServerForm.module.css
|
|
15045
15214
|
(function() {
|
|
15046
15215
|
if (typeof document !== "undefined") {
|
|
@@ -15185,12 +15354,24 @@ function McpServerForm(props) {
|
|
|
15185
15354
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
15186
15355
|
"textarea",
|
|
15187
15356
|
{
|
|
15188
|
-
rows:
|
|
15357
|
+
rows: 3,
|
|
15189
15358
|
value: draft.argsText,
|
|
15190
15359
|
placeholder: t("argsPlaceholder"),
|
|
15191
15360
|
onChange: (event) => update({ argsText: event.currentTarget.value })
|
|
15192
15361
|
}
|
|
15193
|
-
)
|
|
15362
|
+
),
|
|
15363
|
+
(() => {
|
|
15364
|
+
const parsed = parseArgs(draft.argsText);
|
|
15365
|
+
if (parsed.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: ServerForm_default.muted, children: t("argsPreviewEmpty") });
|
|
15366
|
+
const shown = parsed.map((arg) => arg.length > 48 ? `${arg.slice(0, 45)}\u2026` : arg).join(" | ");
|
|
15367
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: ServerForm_default.muted, children: [
|
|
15368
|
+
t("argsPreview"),
|
|
15369
|
+
": ",
|
|
15370
|
+
parsed.length,
|
|
15371
|
+
" \xB7 ",
|
|
15372
|
+
shown
|
|
15373
|
+
] });
|
|
15374
|
+
})()
|
|
15194
15375
|
] }),
|
|
15195
15376
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: ServerForm_default.field, children: [
|
|
15196
15377
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("cwd") }),
|
|
@@ -15202,7 +15383,8 @@ function McpServerForm(props) {
|
|
|
15202
15383
|
placeholder: t("cwdPlaceholder"),
|
|
15203
15384
|
onChange: (event) => update({ cwd: event.currentTarget.value })
|
|
15204
15385
|
}
|
|
15205
|
-
)
|
|
15386
|
+
),
|
|
15387
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: ServerForm_default.muted, children: t("cwdHint") })
|
|
15206
15388
|
] })
|
|
15207
15389
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
15208
15390
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: ServerForm_default.field, children: [
|
|
@@ -15295,11 +15477,11 @@ var ServersJsonEditor_default = { "panel": "panel", "editor": "editor", "hint":
|
|
|
15295
15477
|
// src/client/ServersJsonEditor.tsx
|
|
15296
15478
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
15297
15479
|
function serversToJsonText(servers) {
|
|
15298
|
-
const document2 =
|
|
15480
|
+
const document2 = {};
|
|
15481
|
+
for (const server of servers) {
|
|
15299
15482
|
const entry = {
|
|
15300
|
-
|
|
15301
|
-
|
|
15302
|
-
enabled: server.enabled
|
|
15483
|
+
type: server.transport === "stdio" ? "stdio" : "streamable_http",
|
|
15484
|
+
disabled: !server.enabled
|
|
15303
15485
|
};
|
|
15304
15486
|
if (server.transport === "stdio") {
|
|
15305
15487
|
entry.command = server.command;
|
|
@@ -15314,8 +15496,8 @@ function serversToJsonText(servers) {
|
|
|
15314
15496
|
entry.toolCallTimeoutMs = server.toolCallTimeoutMs;
|
|
15315
15497
|
entry.failOnStartupError = server.failOnStartupError;
|
|
15316
15498
|
entry.env = server.env.map((row) => row.secret ? { name: row.name, secret: true, configured: row.configured } : { name: row.name, secret: false, ...row.value !== void 0 && row.value.length > 0 ? { value: row.value } : {} });
|
|
15317
|
-
|
|
15318
|
-
}
|
|
15499
|
+
document2[server.serverName] = entry;
|
|
15500
|
+
}
|
|
15319
15501
|
return `${JSON.stringify(document2, null, 2)}
|
|
15320
15502
|
`;
|
|
15321
15503
|
}
|
|
@@ -15326,39 +15508,37 @@ function parseServersJson(text) {
|
|
|
15326
15508
|
} catch (error51) {
|
|
15327
15509
|
return { ok: false, error: error51 instanceof Error ? error51.message : String(error51) };
|
|
15328
15510
|
}
|
|
15329
|
-
if (
|
|
15330
|
-
return { ok: false, error:
|
|
15511
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
15512
|
+
return { ok: false, error: 'JSON \u9876\u5C42\u5FC5\u987B\u662F\u5BF9\u8C61\uFF1A{ "\u670D\u52A1\u5668\u540D": { "type": "streamable_http", ... } }' };
|
|
15331
15513
|
}
|
|
15332
|
-
|
|
15514
|
+
const entries = Object.entries(value);
|
|
15515
|
+
if (entries.length === 0) {
|
|
15333
15516
|
return { ok: false, error: "\u914D\u7F6E\u5217\u8868\u4E0D\u80FD\u4E3A\u7A7A" };
|
|
15334
15517
|
}
|
|
15335
15518
|
const servers = [];
|
|
15336
|
-
const
|
|
15337
|
-
|
|
15338
|
-
if (
|
|
15339
|
-
return { ok: false, error: "\
|
|
15340
|
-
}
|
|
15341
|
-
const
|
|
15342
|
-
const
|
|
15343
|
-
|
|
15344
|
-
if (
|
|
15345
|
-
|
|
15346
|
-
|
|
15347
|
-
if (transport
|
|
15348
|
-
return { ok: false, error: `\u670D\u52A1\u5668 "${serverName}" \u7684 transport \u5FC5\u987B\u662F stdio \u6216 streamable-http` };
|
|
15349
|
-
}
|
|
15350
|
-
if (transport === "stdio" && (typeof server.command !== "string" || server.command.trim().length === 0)) {
|
|
15519
|
+
for (const [serverName, raw] of entries) {
|
|
15520
|
+
if (serverName.trim().length === 0) return { ok: false, error: "\u670D\u52A1\u5668\u540D\u4E0D\u80FD\u4E3A\u7A7A" };
|
|
15521
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
15522
|
+
return { ok: false, error: `\u670D\u52A1\u5668 "${serverName}" \u7684\u914D\u7F6E\u5FC5\u987B\u662F\u5BF9\u8C61` };
|
|
15523
|
+
}
|
|
15524
|
+
const cfg = raw;
|
|
15525
|
+
const type = cfg.type;
|
|
15526
|
+
const transport = type === "stdio" ? "stdio" : type === "streamable_http" ? "streamable-http" : void 0;
|
|
15527
|
+
if (transport === void 0) {
|
|
15528
|
+
return { ok: false, error: `\u670D\u52A1\u5668 "${serverName}" \u7684 type \u5FC5\u987B\u662F streamable_http \u6216 stdio` };
|
|
15529
|
+
}
|
|
15530
|
+
if (transport === "stdio" && (typeof cfg.command !== "string" || cfg.command.trim().length === 0)) {
|
|
15351
15531
|
return { ok: false, error: `\u670D\u52A1\u5668 "${serverName}"\uFF08stdio\uFF09\u7F3A\u5C11 command` };
|
|
15352
15532
|
}
|
|
15353
|
-
if (transport === "streamable-http" && typeof
|
|
15354
|
-
return { ok: false, error: `\u670D\u52A1\u5668 "${serverName}"\
|
|
15533
|
+
if (transport === "streamable-http" && typeof cfg.url !== "string") {
|
|
15534
|
+
return { ok: false, error: `\u670D\u52A1\u5668 "${serverName}"\uFF08streamable_http\uFF09\u7F3A\u5C11 url` };
|
|
15355
15535
|
}
|
|
15356
|
-
if (
|
|
15357
|
-
const headers =
|
|
15536
|
+
if (cfg.headers !== void 0) {
|
|
15537
|
+
const headers = cfg.headers;
|
|
15358
15538
|
const okHeaders = headers !== null && typeof headers === "object" && !Array.isArray(headers) || Array.isArray(headers) && headers.every((h) => h !== null && typeof h === "object" && typeof h.name === "string");
|
|
15359
15539
|
if (!okHeaders) return { ok: false, error: `\u670D\u52A1\u5668 "${serverName}" \u7684 headers \u5FC5\u987B\u662F { "\u540D\u79F0": "\u503C" } \u5BF9\u8C61\u6216 { name, value } \u6570\u7EC4` };
|
|
15360
15540
|
}
|
|
15361
|
-
const envValue =
|
|
15541
|
+
const envValue = cfg.env;
|
|
15362
15542
|
const env = Array.isArray(envValue) ? envValue : [];
|
|
15363
15543
|
for (const row of env) {
|
|
15364
15544
|
const r = row;
|
|
@@ -15372,6 +15552,18 @@ function parseServersJson(text) {
|
|
|
15372
15552
|
return { ok: false, error: `\u670D\u52A1\u5668 "${serverName}" \u7684 env \u6761\u76EE "${r.name}" \u7684 value \u5FC5\u987B\u662F\u5B57\u7B26\u4E32` };
|
|
15373
15553
|
}
|
|
15374
15554
|
}
|
|
15555
|
+
const server = {
|
|
15556
|
+
serverName,
|
|
15557
|
+
transport,
|
|
15558
|
+
enabled: cfg.disabled !== true,
|
|
15559
|
+
command: typeof cfg.command === "string" ? cfg.command : "",
|
|
15560
|
+
args: Array.isArray(cfg.args) ? cfg.args : [],
|
|
15561
|
+
cwd: typeof cfg.cwd === "string" ? cfg.cwd : "",
|
|
15562
|
+
url: typeof cfg.url === "string" ? cfg.url : "",
|
|
15563
|
+
headers: cfg.headers,
|
|
15564
|
+
toolCallTimeoutMs: typeof cfg.toolCallTimeoutMs === "number" ? cfg.toolCallTimeoutMs : 6e4,
|
|
15565
|
+
failOnStartupError: cfg.failOnStartupError !== false
|
|
15566
|
+
};
|
|
15375
15567
|
servers.push({ server, env });
|
|
15376
15568
|
}
|
|
15377
15569
|
return { ok: true, servers };
|
|
@@ -16075,9 +16267,12 @@ var zh = {
|
|
|
16075
16267
|
command: "\u547D\u4EE4",
|
|
16076
16268
|
commandPlaceholder: "\u4F8B\u5982 npx",
|
|
16077
16269
|
args: "\u53C2\u6570",
|
|
16078
|
-
argsPlaceholder: "\
|
|
16270
|
+
argsPlaceholder: "\u7A7A\u683C/\u6362\u884C\u5206\u9694\uFF0C\u652F\u6301\u5F15\u53F7\uFF0C\u5982\uFF1A-y @modelcontextprotocol/server-filesystem /path/to/dir",
|
|
16271
|
+
argsPreview: "\u53C2\u6570\u9884\u89C8",
|
|
16272
|
+
argsPreviewEmpty: "\uFF08\u65E0\u53C2\u6570\uFF09",
|
|
16079
16273
|
cwd: "\u5DE5\u4F5C\u76EE\u5F55",
|
|
16080
16274
|
cwdPlaceholder: "\u7559\u7A7A\u7EE7\u627F Host \u5DE5\u4F5C\u76EE\u5F55",
|
|
16275
|
+
cwdHint: "\u7559\u7A7A\u4F1A\u7EE7\u627F DSH \u7684\u5DE5\u4F5C\u76EE\u5F55\u3002\u82E5\u8BE5\u76EE\u5F55\u662F pnpm workspace\uFF0Cnpx \u7B49\u547D\u4EE4\u53EF\u80FD\u89E3\u6790\u5230\u9519\u8BEF\u7684\u672C\u5730\u5305\uFF1B\u5EFA\u8BAE\u586B\u4E00\u4E2A\u666E\u901A\u76EE\u5F55\u7684\u7EDD\u5BF9\u8DEF\u5F84\u3002",
|
|
16081
16276
|
url: "\u670D\u52A1\u5668 URL",
|
|
16082
16277
|
urlPlaceholder: "\u4F8B\u5982 http://localhost:3000/mcp",
|
|
16083
16278
|
headers: "\u8BF7\u6C42\u5934",
|
|
@@ -16132,7 +16327,7 @@ var zh = {
|
|
|
16132
16327
|
toolsCollapse: "\u6536\u8D77\u5DE5\u5177",
|
|
16133
16328
|
serversJson: "JSON \u7EF4\u62A4\u914D\u7F6E",
|
|
16134
16329
|
serversJsonLabel: "\u670D\u52A1\u5668\u914D\u7F6E JSON",
|
|
16135
|
-
serversJsonHint: "\u6574\u4E2A MCP \u670D\u52A1\u5668\u914D\u7F6E\
|
|
16330
|
+
serversJsonHint: "\u6574\u4E2A MCP \u670D\u52A1\u5668\u914D\u7F6E\uFF08key-value \u5BF9\u8C61\uFF0C\u670D\u52A1\u5668\u540D \u2192 \u914D\u7F6E\uFF09\uFF1A\u5E94\u7528\u540E\u5C06\u6309\u6B64\u66FF\u6362\u2014\u2014\u5DF2\u5217\u51FA\u7684\u670D\u52A1\u5668\u521B\u5EFA/\u66F4\u65B0\uFF0C\u672A\u5217\u51FA\u7684\u5C06\u88AB\u5220\u9664\uFF1Btype \u4E3A streamable_http \u6216 stdio\uFF0Cdisabled \u4E3A true \u8868\u793A\u505C\u7528\uFF1Bsecret \u73AF\u5883\u53D8\u91CF\u503C\u4E0D\u4F1A\u5BFC\u51FA\uFF08configured \u6807\u8BB0\uFF09\uFF0C\u7559\u7A7A\u5219\u4FDD\u7559\u539F\u503C\u3002",
|
|
16136
16331
|
serversJsonReseed: "\u91CD\u65B0\u5BFC\u51FA",
|
|
16137
16332
|
serversJsonApply: "\u5E94\u7528",
|
|
16138
16333
|
serversJsonSaving: "\u5E94\u7528\u4E2D\u2026",
|
|
@@ -16173,9 +16368,12 @@ var en = {
|
|
|
16173
16368
|
command: "Command",
|
|
16174
16369
|
commandPlaceholder: "e.g. npx",
|
|
16175
16370
|
args: "Arguments",
|
|
16176
|
-
argsPlaceholder: "
|
|
16371
|
+
argsPlaceholder: "Space/newline separated, quotes supported, e.g. -y @modelcontextprotocol/server-filesystem /path/to/dir",
|
|
16372
|
+
argsPreview: "Argument preview",
|
|
16373
|
+
argsPreviewEmpty: "(no arguments)",
|
|
16177
16374
|
cwd: "Working directory",
|
|
16178
16375
|
cwdPlaceholder: "Leave empty to inherit the Host working directory",
|
|
16376
|
+
cwdHint: "An empty value inherits the DSH working directory. If that directory is a pnpm workspace, npx and similar commands may resolve to a wrong local package; prefer an absolute path to a plain directory.",
|
|
16179
16377
|
url: "Server URL",
|
|
16180
16378
|
urlPlaceholder: "e.g. http://localhost:3000/mcp",
|
|
16181
16379
|
headers: "Headers",
|
|
@@ -16230,7 +16428,7 @@ var en = {
|
|
|
16230
16428
|
toolsCollapse: "Collapse tools",
|
|
16231
16429
|
serversJson: "JSON config editor",
|
|
16232
16430
|
serversJsonLabel: "Server-config JSON",
|
|
16233
|
-
serversJsonHint: "The whole MCP server
|
|
16431
|
+
serversJsonHint: "The whole MCP server config as a key-value object (server name \u2192 config): applying replaces the list \u2014 listed servers are created/updated, servers absent from the document are removed; type is streamable_http or stdio, disabled: true disables the server; secret env values are never exported (a configured flag marks them), a blank value keeps the stored one.",
|
|
16234
16432
|
serversJsonReseed: "Re-export",
|
|
16235
16433
|
serversJsonApply: "Apply",
|
|
16236
16434
|
serversJsonSaving: "Applying\u2026",
|
|
@@ -16255,122 +16453,6 @@ var en = {
|
|
|
16255
16453
|
configTitle: "MCP config"
|
|
16256
16454
|
};
|
|
16257
16455
|
|
|
16258
|
-
// src/client/mcp-store.ts
|
|
16259
|
-
var import_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
16260
|
-
var DEFAULT_TOOL_CALL_TIMEOUT_MS = 6e4;
|
|
16261
|
-
function emptyDraft() {
|
|
16262
|
-
return {
|
|
16263
|
-
id: null,
|
|
16264
|
-
serverName: "",
|
|
16265
|
-
transport: "stdio",
|
|
16266
|
-
enabled: true,
|
|
16267
|
-
command: "",
|
|
16268
|
-
argsText: "",
|
|
16269
|
-
cwd: "",
|
|
16270
|
-
url: "",
|
|
16271
|
-
headersText: "",
|
|
16272
|
-
toolCallTimeoutMs: String(DEFAULT_TOOL_CALL_TIMEOUT_MS),
|
|
16273
|
-
// Reject the mount by default when the startup connection fails, so a
|
|
16274
|
-
// broken server never registers stale tools.
|
|
16275
|
-
failOnStartupError: true
|
|
16276
|
-
};
|
|
16277
|
-
}
|
|
16278
|
-
function draftFromServer(server) {
|
|
16279
|
-
return {
|
|
16280
|
-
id: server.id,
|
|
16281
|
-
serverName: server.serverName,
|
|
16282
|
-
transport: server.transport,
|
|
16283
|
-
enabled: server.enabled,
|
|
16284
|
-
command: server.command,
|
|
16285
|
-
argsText: server.args.join("\n"),
|
|
16286
|
-
cwd: server.cwd,
|
|
16287
|
-
url: server.url,
|
|
16288
|
-
headersText: server.headers.map((header) => `${header.name}: ${header.value}`).join("\n"),
|
|
16289
|
-
toolCallTimeoutMs: String(server.toolCallTimeoutMs),
|
|
16290
|
-
failOnStartupError: server.failOnStartupError
|
|
16291
|
-
};
|
|
16292
|
-
}
|
|
16293
|
-
function parseLines(text) {
|
|
16294
|
-
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
16295
|
-
}
|
|
16296
|
-
function parseHeaders(text) {
|
|
16297
|
-
const headers = [];
|
|
16298
|
-
for (const line of parseLines(text)) {
|
|
16299
|
-
const separator = line.indexOf(":");
|
|
16300
|
-
if (separator <= 0) continue;
|
|
16301
|
-
const name = line.slice(0, separator).trim();
|
|
16302
|
-
const value = line.slice(separator + 1).trim();
|
|
16303
|
-
if (name.length > 0) headers.push({ name, value });
|
|
16304
|
-
}
|
|
16305
|
-
return headers;
|
|
16306
|
-
}
|
|
16307
|
-
function parseTimeout(text) {
|
|
16308
|
-
const value = Number(text);
|
|
16309
|
-
return Number.isInteger(value) && value > 0 ? value : DEFAULT_TOOL_CALL_TIMEOUT_MS;
|
|
16310
|
-
}
|
|
16311
|
-
function draftToSubmission(draft) {
|
|
16312
|
-
return {
|
|
16313
|
-
server: {
|
|
16314
|
-
serverName: draft.serverName.trim(),
|
|
16315
|
-
transport: draft.transport,
|
|
16316
|
-
enabled: draft.enabled,
|
|
16317
|
-
command: draft.command.trim(),
|
|
16318
|
-
args: parseLines(draft.argsText),
|
|
16319
|
-
cwd: draft.cwd.trim(),
|
|
16320
|
-
url: draft.url.trim(),
|
|
16321
|
-
headers: parseHeaders(draft.headersText),
|
|
16322
|
-
toolCallTimeoutMs: parseTimeout(draft.toolCallTimeoutMs),
|
|
16323
|
-
failOnStartupError: draft.failOnStartupError
|
|
16324
|
-
}
|
|
16325
|
-
};
|
|
16326
|
-
}
|
|
16327
|
-
function createMcpManagerStore() {
|
|
16328
|
-
return (0, import_client.defineStore)({
|
|
16329
|
-
init: () => ({
|
|
16330
|
-
loadState: "loading",
|
|
16331
|
-
servers: [],
|
|
16332
|
-
draft: null,
|
|
16333
|
-
busy: null,
|
|
16334
|
-
testRunning: false,
|
|
16335
|
-
test: null
|
|
16336
|
-
}),
|
|
16337
|
-
actions: {
|
|
16338
|
-
setLoadState: (d, state) => {
|
|
16339
|
-
d.loadState = state;
|
|
16340
|
-
},
|
|
16341
|
-
setServers: (d, servers) => {
|
|
16342
|
-
d.servers = servers;
|
|
16343
|
-
},
|
|
16344
|
-
beginCreate: (d) => {
|
|
16345
|
-
d.draft = emptyDraft();
|
|
16346
|
-
d.test = null;
|
|
16347
|
-
},
|
|
16348
|
-
beginEdit: (d, server) => {
|
|
16349
|
-
d.draft = draftFromServer(server);
|
|
16350
|
-
d.test = null;
|
|
16351
|
-
},
|
|
16352
|
-
cancelEdit: (d) => {
|
|
16353
|
-
d.draft = null;
|
|
16354
|
-
d.test = null;
|
|
16355
|
-
d.busy = null;
|
|
16356
|
-
},
|
|
16357
|
-
updateDraft: (d, patch) => {
|
|
16358
|
-
if (d.draft === null) return;
|
|
16359
|
-
d.draft = { ...d.draft, ...patch };
|
|
16360
|
-
},
|
|
16361
|
-
setBusy: (d, busy) => {
|
|
16362
|
-
d.busy = busy;
|
|
16363
|
-
},
|
|
16364
|
-
setTestRunning: (d, running) => {
|
|
16365
|
-
d.testRunning = running;
|
|
16366
|
-
},
|
|
16367
|
-
setTest: (d, test) => {
|
|
16368
|
-
d.test = test;
|
|
16369
|
-
}
|
|
16370
|
-
}
|
|
16371
|
-
});
|
|
16372
|
-
}
|
|
16373
|
-
|
|
16374
16456
|
// src/client/index.ts
|
|
16375
16457
|
var NS = "settings.mcp";
|
|
16376
16458
|
var inject = ["slots", "locale", "remote"];
|