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 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.6.0`).
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.6.0`)。
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: 2,
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 = servers.map((server) => {
15480
+ const document2 = {};
15481
+ for (const server of servers) {
15299
15482
  const entry = {
15300
- serverName: server.serverName,
15301
- transport: server.transport,
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
- return entry;
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 (!Array.isArray(value)) {
15330
- return { ok: false, error: "JSON \u9876\u5C42\u5FC5\u987B\u662F\u670D\u52A1\u5668\u914D\u7F6E\u6570\u7EC4\uFF1A[ { serverName, transport, ... }, ... ]" };
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
- if (value.length === 0) {
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 seen = /* @__PURE__ */ new Set();
15337
- for (const item of value) {
15338
- if (item === null || typeof item !== "object" || Array.isArray(item)) {
15339
- return { ok: false, error: "\u6BCF\u4E2A\u6761\u76EE\u5FC5\u987B\u662F\u5BF9\u8C61\uFF1A{ serverName, transport, ... }" };
15340
- }
15341
- const server = item;
15342
- const serverName = typeof server.serverName === "string" ? server.serverName.trim() : "";
15343
- if (serverName.length === 0) return { ok: false, error: "serverName \u4E0D\u80FD\u4E3A\u7A7A" };
15344
- if (seen.has(serverName)) return { ok: false, error: `serverName "${serverName}" \u91CD\u590D` };
15345
- seen.add(serverName);
15346
- const transport = server.transport;
15347
- if (transport !== "stdio" && transport !== "streamable-http") {
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 server.url !== "string") {
15354
- return { ok: false, error: `\u670D\u52A1\u5668 "${serverName}"\uFF08streamable-http\uFF09\u7F3A\u5C11 url` };
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 (server.headers !== void 0) {
15357
- const headers = server.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 = server.env;
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: "\u6BCF\u884C\u4E00\u4E2A\u53C2\u6570",
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\u5217\u8868\uFF08\u6570\u7EC4\uFF09\uFF1A\u5E94\u7528\u540E\u5C06\u6309\u6B64\u5217\u8868\u66FF\u6362\u2014\u2014\u5DF2\u5217\u51FA\u7684\u670D\u52A1\u5668\u521B\u5EFA/\u66F4\u65B0\uFF0C\u672A\u5217\u51FA\u7684\u5C06\u88AB\u5220\u9664\uFF1Bsecret \u73AF\u5883\u53D8\u91CF\u503C\u4E0D\u4F1A\u5BFC\u51FA\uFF08configured \u6807\u8BB0\uFF09\uFF0C\u7559\u7A7A\u5219\u4FDD\u7559\u539F\u503C\u3002",
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: "One argument per line",
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 list (array): applying replaces the list \u2014 listed servers are created/updated, existing servers absent from the document are removed; secret env values are never exported (a configured flag marks them), a blank value keeps the stored one.",
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"];