dsh-mcp 1.2.0 → 1.3.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,18 @@ 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
+ ## [1.3.0] - 2026-08-16
11
+
12
+ ### Added
13
+
14
+ - **Windows working-directory support**: stdio servers now accept drive-letter absolute paths for `cwd` (e.g. `C:\Users\...`, `C:/...`), consistent with POSIX `/` and UNC `\\` paths (PR #2, thanks @coding-chong)
15
+
16
+ ### Fixed
17
+
18
+ - Form operations now surface the real error: save / delete / test-connection failures show `code: message` (e.g. `MCP_SERVER_NAME_CONFLICT: serverName "x" is already used...`) instead of a generic message, making failures diagnosable
19
+ - Refresh is decoupled from save/delete: a `refresh()` failure no longer misreports the save/delete outcome — the editor stays open and shows the refresh failure reason (`refresh()` keeps its try/catch and returns a result)
20
+ - Removed the now-unused `failureLocaleKey` dead code (error display shows `code: message` directly)
21
+
10
22
  ## [1.2.0] - 2026-08-16
11
23
 
12
24
  ### Added
package/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@
7
7
  格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
8
8
  版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
9
9
 
10
+ ## [1.3.0] - 2026-08-16
11
+
12
+ ### 新增
13
+
14
+ - **Windows 工作目录支持**:stdio 服务器的 cwd 接受 Windows 盘符绝对路径(如 `C:\Users\...`、`C:/...`),与 POSIX `/`、UNC `\\` 路径一致(PR #2,感谢 @coding-chong)
15
+
16
+ ### 修复
17
+
18
+ - 表单操作失败时展示真实错误信息:保存 / 删除 / 测试连接失败不再只显示笼统文案,直接展示 `code: message`(如 `MCP_SERVER_NAME_CONFLICT: serverName "x" is already used...`),便于定位问题
19
+ - 刷新与保存/删除解耦:`refresh()` 失败不再误报保存结果,停留编辑页并显示刷新失败原因(`refresh()` 保留 try/catch 并返回结果)
20
+ - 清理死代码:移除已无引用的 `failureLocaleKey`(错误展示改为直接显示 `code: message`)
21
+
10
22
  ## [1.2.0] - 2026-08-16
11
23
 
12
24
  ### 新增
package/lib/client.js CHANGED
@@ -14971,20 +14971,6 @@ function draftToSubmission(draft) {
14971
14971
  }))
14972
14972
  };
14973
14973
  }
14974
- function failureLocaleKey(code) {
14975
- switch (code) {
14976
- case "MCP_SERVER_NAME_CONFLICT":
14977
- return "serverNameConflict";
14978
- case "MCP_SERVER_NOT_FOUND":
14979
- return "notFound";
14980
- case "MCP_INVALID_SPEC":
14981
- return "invalidSpec";
14982
- case "MCP_MOUNT_FAILED":
14983
- return "mountFailed";
14984
- default:
14985
- return "failureTitle";
14986
- }
14987
- }
14988
14974
  function createMcpManagerStore() {
14989
14975
  return (0, import_client.defineStore)({
14990
14976
  init: () => ({
@@ -15065,13 +15051,16 @@ function McpServerForm(props) {
15065
15051
  const addEnvRow = () => {
15066
15052
  update({ env: [...draft.env, createEnvRowDraft()] });
15067
15053
  };
15054
+ const errorText = (error51) => error51 instanceof Error ? error51.message : String(error51);
15068
15055
  const runTest = async () => {
15069
15056
  setSaveError(null);
15070
15057
  actions.setTestRunning(true);
15071
15058
  try {
15072
15059
  actions.setTest(await injected.test(draft));
15073
- } catch {
15060
+ } catch (error51) {
15061
+ console.error("[dsh-mcp] test failed:", error51);
15074
15062
  actions.setTest(null);
15063
+ setSaveError(errorText(error51));
15075
15064
  } finally {
15076
15065
  actions.setTestRunning(false);
15077
15066
  }
@@ -15082,14 +15071,15 @@ function McpServerForm(props) {
15082
15071
  try {
15083
15072
  const failure = await injected.save(draft);
15084
15073
  if (failure !== null) {
15085
- setSaveError(t(failureLocaleKey(failure.code)));
15074
+ setSaveError(`${failure.code}: ${failure.message}`);
15086
15075
  return;
15087
15076
  }
15088
- await refresh();
15077
+ if (!await refresh()) return;
15089
15078
  actions.cancelEdit();
15090
15079
  onSaved?.();
15091
- } catch {
15092
- setSaveError(t("failureTitle"));
15080
+ } catch (error51) {
15081
+ console.error("[dsh-mcp] save failed:", error51);
15082
+ setSaveError(errorText(error51));
15093
15083
  } finally {
15094
15084
  actions.setBusy(null);
15095
15085
  }
@@ -15101,13 +15091,14 @@ function McpServerForm(props) {
15101
15091
  try {
15102
15092
  const failure = await injected.remove(draft.id);
15103
15093
  if (failure !== null) {
15104
- setSaveError(failure.message);
15094
+ setSaveError(`${failure.code}: ${failure.message}`);
15105
15095
  return;
15106
15096
  }
15107
- await refresh();
15097
+ if (!await refresh()) return;
15108
15098
  actions.cancelEdit();
15109
- } catch {
15110
- setSaveError(t("failureTitle"));
15099
+ } catch (error51) {
15100
+ console.error("[dsh-mcp] remove failed:", error51);
15101
+ setSaveError(errorText(error51));
15111
15102
  } finally {
15112
15103
  actions.setBusy(null);
15113
15104
  }
@@ -15115,7 +15106,11 @@ function McpServerForm(props) {
15115
15106
  const refresh = async () => {
15116
15107
  try {
15117
15108
  actions.setServers(await injected.list());
15118
- } catch {
15109
+ return true;
15110
+ } catch (error51) {
15111
+ console.error("[dsh-mcp] refresh failed:", error51);
15112
+ setSaveError(errorText(error51));
15113
+ return false;
15119
15114
  }
15120
15115
  };
15121
15116
  const stdio = draft.transport === "stdio";
package/lib/index.js CHANGED
@@ -105,7 +105,7 @@ function validateServerInput(server, env) {
105
105
  if (!serverNameSchema.safeParse(server.serverName).success) throw new McpServerValidationError("serverName must match [A-Za-z0-9_-]{1,32}");
106
106
  if (server.transport === "stdio") {
107
107
  if (server.command.trim().length === 0) throw new McpServerValidationError("stdio transport requires a command");
108
- if (server.cwd.length > 0 && !/^[/\\]/.test(server.cwd)) throw new McpServerValidationError("cwd must be an absolute path or empty");
108
+ if (server.cwd.length > 0 && !/^(?:[A-Za-z]:[\\/]|[/\\])/.test(server.cwd)) throw new McpServerValidationError("cwd must be an absolute path or empty");
109
109
  } else try {
110
110
  const parsed = new URL(server.url);
111
111
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error("non-http protocol");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mcp",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "MCP 管理界面 + tool search:可视化配置/启停/刷新 MCP 服务器与工具勾选,按需检索(tool search)热注入;工具列表稳定命中缓存、不撑爆上下文。DeepSeek Harness plugin: MCP management UI + tool search — stable tool list, cache-friendly, no context bloat.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -1,16 +1,16 @@
1
1
  import { useState, type ReactNode } from 'react'
2
- import type { McpServerId, McpServerView } from './types.ts'
2
+ import type { McpManagerFailure, McpServerId, McpServerView } from './types.ts'
3
3
  import type {
4
4
  McpDraft, McpEnvRowDraft, McpTestOutcome,
5
5
  } from './mcp-store.ts'
6
- import { createEnvRowDraft, failureLocaleKey } from './mcp-store.ts'
6
+ import { createEnvRowDraft } from './mcp-store.ts'
7
7
  import type { McpSettingsLocaleKey } from './locales.ts'
8
8
  import css from './ServerForm.module.css'
9
9
 
10
10
  /** The Remote verbs the editor needs; structurally the section's inject face. */
11
11
  export interface McpServerFormRemote {
12
- save: (draft: McpDraft) => Promise<{ message: string } | null>
13
- remove: (id: McpServerId) => Promise<{ message: string } | null>
12
+ save: (draft: McpDraft) => Promise<McpManagerFailure | null>
13
+ remove: (id: McpServerId) => Promise<McpManagerFailure | null>
14
14
  test: (draft: McpDraft) => Promise<McpTestOutcome>
15
15
  list: () => Promise<readonly McpServerView[]>
16
16
  }
@@ -60,13 +60,18 @@ export function McpServerForm(props: McpServerFormProps): ReactNode {
60
60
  update({ env: [...draft.env, createEnvRowDraft()] })
61
61
  }
62
62
 
63
+ const errorText = (error: unknown): string =>
64
+ error instanceof Error ? error.message : String(error)
65
+
63
66
  const runTest = async (): Promise<void> => {
64
67
  setSaveError(null)
65
68
  actions.setTestRunning(true)
66
69
  try {
67
70
  actions.setTest(await injected.test(draft))
68
- } catch {
71
+ } catch (error) {
72
+ console.error('[dsh-mcp] test failed:', error)
69
73
  actions.setTest(null)
74
+ setSaveError(errorText(error))
70
75
  } finally {
71
76
  actions.setTestRunning(false)
72
77
  }
@@ -78,14 +83,15 @@ export function McpServerForm(props: McpServerFormProps): ReactNode {
78
83
  try {
79
84
  const failure = await injected.save(draft)
80
85
  if (failure !== null) {
81
- setSaveError(t(failureLocaleKey(failure.code)))
86
+ setSaveError(`${failure.code}: ${failure.message}`)
82
87
  return
83
88
  }
84
- await refresh()
89
+ if (!await refresh()) return
85
90
  actions.cancelEdit()
86
91
  onSaved?.()
87
- } catch {
88
- setSaveError(t('failureTitle'))
92
+ } catch (error) {
93
+ console.error('[dsh-mcp] save failed:', error)
94
+ setSaveError(errorText(error))
89
95
  } finally {
90
96
  actions.setBusy(null)
91
97
  }
@@ -98,24 +104,27 @@ export function McpServerForm(props: McpServerFormProps): ReactNode {
98
104
  try {
99
105
  const failure = await injected.remove(draft.id)
100
106
  if (failure !== null) {
101
- setSaveError(failure.message)
107
+ setSaveError(`${failure.code}: ${failure.message}`)
102
108
  return
103
109
  }
104
- await refresh()
110
+ if (!await refresh()) return
105
111
  actions.cancelEdit()
106
- } catch {
107
- setSaveError(t('failureTitle'))
112
+ } catch (error) {
113
+ console.error('[dsh-mcp] remove failed:', error)
114
+ setSaveError(errorText(error))
108
115
  } finally {
109
116
  actions.setBusy(null)
110
117
  }
111
118
  }
112
119
 
113
- const refresh = async (): Promise<void> => {
120
+ const refresh = async (): Promise<boolean> => {
114
121
  try {
115
122
  actions.setServers(await injected.list())
116
- } catch {
117
- // The save/remove outcome is the actionable one; a failed refresh is
118
- // re-run on the next page visit.
123
+ return true
124
+ } catch (error) {
125
+ console.error('[dsh-mcp] refresh failed:', error)
126
+ setSaveError(errorText(error))
127
+ return false
119
128
  }
120
129
  }
121
130
 
@@ -188,17 +188,6 @@ export function draftToSubmission(draft: McpDraft): McpSubmission {
188
188
  }
189
189
  }
190
190
 
191
- /** Map a Remote failure code to a locale key for user-facing copy. */
192
- export function failureLocaleKey(code: string): string {
193
- switch (code) {
194
- case 'MCP_SERVER_NAME_CONFLICT': return 'serverNameConflict'
195
- case 'MCP_SERVER_NOT_FOUND': return 'notFound'
196
- case 'MCP_INVALID_SPEC': return 'invalidSpec'
197
- case 'MCP_MOUNT_FAILED': return 'mountFailed'
198
- default: return 'failureTitle'
199
- }
200
- }
201
-
202
191
  /**
203
192
  * Declares the MCP management page state and write surface.
204
193
  * @returns the store handle.