dsh-mcp 1.9.0 → 1.10.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 +11 -1
- package/CHANGELOG.md +11 -1
- package/README.en.md +16 -1
- package/README.md +15 -1
- package/lib/client.js +12 -3
- package/lib/host-locales.js +105 -0
- package/lib/index.js +45 -16
- package/lib/mcp-client.js +10 -4
- package/lib/oauth.js +25 -6
- package/package.json +2 -3
- package/scripts/build.mjs +1 -4
- package/src/client/McpSettingsSection.tsx +11 -2
- package/src/client/index.ts +1 -1
- package/src/client/locales.ts +2 -0
- package/src/client/mcp-store.ts +1 -1
package/CHANGELOG.en.md
CHANGED
|
@@ -7,7 +7,17 @@ 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
|
-
## [
|
|
10
|
+
## [1.10.0] - 2026-09-02
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Actionable diagnosis when the host half is missing**: when the server-list load fails because of an HTTP 404 on `/api/mcpManager/*` (plugin host half not registered, or client/host version mismatch), the page now shows a troubleshooting hint next to the raw error (verify the `cordis.patch.yml` row → restart `dsh web` → hard-refresh → upgrade both sides); the README troubleshooting section is updated as well
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **Host-side copy internationalization**: the settings UI was fully bilingual but the host half (`lib/index.js`/`lib/oauth.js`/`lib/mcp-client.js`) kept model-visible copy and OAuth errors hardcoded in Chinese. The `mcp_tool_search` description/parameter docs, search-result text, the injected `mcp-tool-control` system prompt, and the OAuth authorization/callback page copy now follow the DSH `locale.preference` from the settings document (new `lib/host-locales.js` table; falls back to Chinese — the previous behavior — when the preference cannot be read)
|
|
19
|
+
- **OAuth decisions no longer key on message text**: the tool-call OAuth preflight used `error.message.includes("授权")` to decide whether to re-throw the link-carrying error — translating the message would silently break OAuth error propagation. It now keys on a stable error code `MCP_OAUTH_REQUIRED` (`error.code`), decoupling control flow from display text
|
|
20
|
+
- **Static-credential servers are no longer mistaken for OAuth**: only streamable-http servers configured for authorization-code + PKCE and WITHOUT a static `Authorization` request header get an OAuth provider; a static-token server facing a 401 now reports the authentication failure instead of starting a browser authorization flow
|
|
11
21
|
|
|
12
22
|
## [1.9.0] - 2026-08-28
|
|
13
23
|
|
package/CHANGELOG.md
CHANGED
|
@@ -7,7 +7,17 @@
|
|
|
7
7
|
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
|
|
8
8
|
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
9
9
|
|
|
10
|
-
## [
|
|
10
|
+
## [1.10.0] - 2026-09-02
|
|
11
|
+
|
|
12
|
+
### 新增
|
|
13
|
+
|
|
14
|
+
- **设置页对 host 缺失给出可操作诊断**:服务器列表加载失败若源于 `/api/mcpManager/*` HTTP 404(插件 host 半未注册或 client/host 版本不匹配),页面除原始错误外额外显示排查指引(核对 `cordis.patch.yml` 注册行 → 重启 `dsh web` → 硬刷新 → 同步升级),README 故障排查同步补充
|
|
15
|
+
|
|
16
|
+
### 修复
|
|
17
|
+
|
|
18
|
+
- **host 端文案国际化**:设置页双语早已齐备,但 host 半部(`lib/index.js`/`lib/oauth.js`/`lib/mcp-client.js`)的模型可见文案与 OAuth 错误一直是硬编码中文——`mcp_tool_search` 描述与参数说明、检索结果文案、注入的 `mcp-tool-control` 系统提示、OAuth 授权/回调页文案现均按 DSH `locale.preference`(settings 文档)选择 zh/en(新增 `lib/host-locales.js` 文案表;读不到时回退中文,与旧行为一致)
|
|
19
|
+
- **OAuth 判定不再依赖文本匹配**:`mcp-client` 的工具授权预检曾用 `error.message.includes("授权")` 决定是否重抛带链接的错误,翻译消息会静默破坏 OAuth 错误传播——现统一改用稳定的错误码 `MCP_OAUTH_REQUIRED`(`error.code`),控制流与显示文案解耦
|
|
20
|
+
- **静态凭据服务器不再被误当作 OAuth**:只有配置了 OAuth 授权码 + PKCE 且**没有**静态 `Authorization` 请求头的 streamable-http 服务器才挂 OAuth provider;带静态 token 的服务器遇到 401 时直接呈现认证失败,不再触发浏览器授权流程
|
|
11
21
|
|
|
12
22
|
## [1.9.0] - 2026-08-28
|
|
13
23
|
|
package/README.en.md
CHANGED
|
@@ -160,7 +160,12 @@ Check in order:
|
|
|
160
160
|
- Check the `dsh web` process log for `mcp-manager` initialization errors;
|
|
161
161
|
- After upgrading the plugin, restart and **hard-refresh** so the old client bundle does not
|
|
162
162
|
mix with the new host (typical symptom: `client api: ... 404` or `env is not iterable` — both
|
|
163
|
-
come from mixing versions)
|
|
163
|
+
come from mixing versions);
|
|
164
|
+
- An error shaped like `transport failure for /api/mcpManager/list: HTTP 404` means the host did
|
|
165
|
+
not register the `mcpManager` service: usually the plugin host half is not active (missing
|
|
166
|
+
cordis.patch.yml row / wrong profile) or the client and host versions disagree. Verify the
|
|
167
|
+
registration row per Q1, confirm the install targets the `web` profile, restart, and
|
|
168
|
+
hard-refresh; if it persists, upgrade both `dsh web` and the plugin to the latest versions.
|
|
164
169
|
|
|
165
170
|
**Q3: MCP tools do not show up in an agent session?**
|
|
166
171
|
|
|
@@ -169,6 +174,16 @@ Check in order:
|
|
|
169
174
|
so tools not searched are absent from the system prompt by design; switch to "Full injection"
|
|
170
175
|
to verify.
|
|
171
176
|
|
|
177
|
+
**Q4: A server with an `Authorization` header still asks for OAuth / fails to mount?**
|
|
178
|
+
|
|
179
|
+
- When an `Authorization` (static bearer/token) header is configured, dsh-mcp does NOT treat the
|
|
180
|
+
server as OAuth: the authorization-code + PKCE flow is enabled only for servers WITHOUT a static
|
|
181
|
+
`Authorization` header, so a 401 is never mistaken for an OAuth challenge that opens the browser.
|
|
182
|
+
For a static-token server, make sure the request headers are correct;
|
|
183
|
+
- If an HTTPS intranet host reports `fetch failed` / `unable to verify the first certificate`, the
|
|
184
|
+
host Node does not trust the internal CA: start `dsh web` with `NODE_OPTIONS=--use-system-ca`
|
|
185
|
+
(or add the root cert to `NODE_EXTRA_CA_CERTS`), then restart the host and hard-refresh.
|
|
186
|
+
|
|
172
187
|
**Add a server**:
|
|
173
188
|
|
|
174
189
|
1. Click **Add server** (the form expands inline above the list).
|
package/README.md
CHANGED
|
@@ -142,7 +142,11 @@ dsh plugin --profile web add link:<本仓库绝对路径>
|
|
|
142
142
|
|
|
143
143
|
- 确认 `dsh web` 进程日志中 `mcp-manager` 没有初始化错误;
|
|
144
144
|
- 若升级过插件,请重启后**硬刷新**,避免旧 client bundle 与新版 host 不匹配
|
|
145
|
-
(典型现象:操作报 `client api: ... 404` 或 `env is not iterable
|
|
145
|
+
(典型现象:操作报 `client api: ... 404` 或 `env is not iterable`,都是新旧版本混用所致);
|
|
146
|
+
- 报错形如 `transport failure for /api/mcpManager/list: HTTP 404` 表示宿主端没有注册
|
|
147
|
+
`mcpManager` 服务:多半是插件 host 半未生效(漏了 cordis.patch.yml 注册行/装错 profile)或
|
|
148
|
+
client 与 host 版本不一致。请按 Q1 核对注册行、确认安装到了 `web` profile、重启后硬刷新;
|
|
149
|
+
仍不行则把 `dsh web` 与插件版本都升到最新再试。
|
|
146
150
|
|
|
147
151
|
**Q3:MCP 工具没有出现在 agent 会话里?**
|
|
148
152
|
|
|
@@ -150,6 +154,16 @@ dsh plugin --profile web add link:<本仓库绝对路径>
|
|
|
150
154
|
- 注入模式为「按需检索」时,模型会通过 `mcp_tool_search` 检索后热注入,未检索到的工具不在
|
|
151
155
|
系统提示词中属正常现象;可切换到「全量注入」验证。
|
|
152
156
|
|
|
157
|
+
**Q4:服务器配置了 Authorization 头却提示需要 OAuth 授权 / 挂载失败?**
|
|
158
|
+
|
|
159
|
+
- 只要在请求头里配置了 `Authorization`(静态 Bearer/token),dsh-mcp 就不会把它当作 OAuth
|
|
160
|
+
服务器:真正的 OAuth(授权码 + PKCE)只对**没有静态 Authorization 头**的服务器启用,避免
|
|
161
|
+
401 被误当成 OAuth 挑战而打开浏览器授权。若你连的是需要静态 token 的服务器,确认请求头
|
|
162
|
+
正确即可;
|
|
163
|
+
- 若 https 内网域名报 `fetch failed` / `unable to verify the first certificate`,是宿主 Node
|
|
164
|
+
不信任公司内网 CA:用 `NODE_OPTIONS=--use-system-ca` 启动 `dsh web`(或把根证书加入
|
|
165
|
+
`NODE_EXTRA_CA_CERTS`),再重启宿主与硬刷新浏览器。
|
|
166
|
+
|
|
153
167
|
**添加服务器**:
|
|
154
168
|
|
|
155
169
|
1. 点击「添加服务器」(表单在列表上方就地展开)
|
package/lib/client.js
CHANGED
|
@@ -15042,7 +15042,7 @@ var import_react4 = require("react");
|
|
|
15042
15042
|
var import_react = require("react");
|
|
15043
15043
|
|
|
15044
15044
|
// src/client/mcp-store.ts
|
|
15045
|
-
var
|
|
15045
|
+
var import_dsh_client_store = require("@deepseek-ai/dsh-client-store");
|
|
15046
15046
|
var DEFAULT_TOOL_CALL_TIMEOUT_MS = 6e4;
|
|
15047
15047
|
function emptyDraft() {
|
|
15048
15048
|
return {
|
|
@@ -15164,7 +15164,7 @@ function draftToSubmission(draft) {
|
|
|
15164
15164
|
};
|
|
15165
15165
|
}
|
|
15166
15166
|
function createMcpManagerStore() {
|
|
15167
|
-
return (0,
|
|
15167
|
+
return (0, import_dsh_client_store.defineStore)({
|
|
15168
15168
|
init: () => ({
|
|
15169
15169
|
loadState: "loading",
|
|
15170
15170
|
servers: [],
|
|
@@ -15934,6 +15934,7 @@ function McpSettingsSection(props) {
|
|
|
15934
15934
|
const { setLoadState, setServers, beginCreate, beginEdit, cancelEdit, updateDraft, setBusy, setTestRunning, setTest } = props.actions;
|
|
15935
15935
|
const t = props.t;
|
|
15936
15936
|
const [loadErrorDetail, setLoadErrorDetail] = (0, import_react4.useState)(null);
|
|
15937
|
+
const [hostMissing, setHostMissing] = (0, import_react4.useState)(false);
|
|
15937
15938
|
const [tools, setTools] = (0, import_react4.useState)(null);
|
|
15938
15939
|
const [toolsError, setToolsError] = (0, import_react4.useState)(null);
|
|
15939
15940
|
const [expanded, setExpanded] = (0, import_react4.useState)(/* @__PURE__ */ new Set());
|
|
@@ -15960,7 +15961,9 @@ function McpSettingsSection(props) {
|
|
|
15960
15961
|
};
|
|
15961
15962
|
const fail = (error51) => {
|
|
15962
15963
|
console.error("[dsh-mcp] list failed:", error51);
|
|
15963
|
-
|
|
15964
|
+
const message = String((error51 instanceof Error ? error51.message : error51) ?? error51);
|
|
15965
|
+
setLoadErrorDetail(message);
|
|
15966
|
+
setHostMissing(/HTTP 404|transport failure/.test(message));
|
|
15964
15967
|
setLoadState("error");
|
|
15965
15968
|
};
|
|
15966
15969
|
const load = () => {
|
|
@@ -15969,6 +15972,7 @@ function McpSettingsSection(props) {
|
|
|
15969
15972
|
setServers(servers);
|
|
15970
15973
|
setLoadState("ready");
|
|
15971
15974
|
setLoadErrorDetail(null);
|
|
15975
|
+
setHostMissing(false);
|
|
15972
15976
|
},
|
|
15973
15977
|
(error51) => fail(error51)
|
|
15974
15978
|
);
|
|
@@ -15982,6 +15986,7 @@ function McpSettingsSection(props) {
|
|
|
15982
15986
|
setServers(servers);
|
|
15983
15987
|
setLoadState("ready");
|
|
15984
15988
|
setLoadErrorDetail(null);
|
|
15989
|
+
setHostMissing(false);
|
|
15985
15990
|
},
|
|
15986
15991
|
(error51) => {
|
|
15987
15992
|
if (current) fail(error51);
|
|
@@ -16026,6 +16031,7 @@ function McpSettingsSection(props) {
|
|
|
16026
16031
|
(servers) => {
|
|
16027
16032
|
setServers(servers);
|
|
16028
16033
|
setLoadState("ready");
|
|
16034
|
+
setHostMissing(false);
|
|
16029
16035
|
},
|
|
16030
16036
|
() => {
|
|
16031
16037
|
}
|
|
@@ -16074,6 +16080,7 @@ function McpSettingsSection(props) {
|
|
|
16074
16080
|
if (state.loadState === "error") {
|
|
16075
16081
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: McpSettingsSection_default.failure, children: [
|
|
16076
16082
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { role: "alert", children: t("loadError") }),
|
|
16083
|
+
hostMissing ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: McpSettingsSection_default.muted, children: t("loadErrorHostMissing") }) : null,
|
|
16077
16084
|
loadErrorDetail !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("pre", { className: McpSettingsSection_default.muted, children: loadErrorDetail }) : null,
|
|
16078
16085
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", onClick: () => {
|
|
16079
16086
|
setLoadState("loading");
|
|
@@ -16254,6 +16261,7 @@ var zh = {
|
|
|
16254
16261
|
nav: "MCP",
|
|
16255
16262
|
loading: "\u6B63\u5728\u8BFB\u53D6 MCP \u670D\u52A1\u5668\u2026",
|
|
16256
16263
|
loadError: "\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6 MCP \u670D\u52A1\u5668\u3002",
|
|
16264
|
+
loadErrorHostMissing: "\u5BBF\u4E3B\u7AEF\u672A\u63D0\u4F9B mcpManager \u670D\u52A1\uFF08HTTP 404\uFF09\u3002\u901A\u5E38\u662F\u63D2\u4EF6 host \u534A\u672A\u6CE8\u518C\u6216 client/host \u7248\u672C\u4E0D\u5339\u914D\uFF1A\u8BF7\u786E\u8BA4 cordis.patch.yml \u5DF2\u6CE8\u518C dsh-mcp \u884C\u3001\u91CD\u542F dsh web \u5E76\u786C\u5237\u65B0\uFF08Cmd/Ctrl+Shift+R\uFF09\uFF1B\u82E5\u5347\u7EA7\u8FC7\u63D2\u4EF6\u8BF7\u540C\u6B65\u5347\u7EA7\u5BBF\u4E3B\u3002",
|
|
16257
16265
|
retry: "\u91CD\u8BD5",
|
|
16258
16266
|
empty: "\u8FD8\u6CA1\u6709\u914D\u7F6E MCP \u670D\u52A1\u5668\u3002",
|
|
16259
16267
|
addServer: "\u6DFB\u52A0\u670D\u52A1\u5668",
|
|
@@ -16355,6 +16363,7 @@ var en = {
|
|
|
16355
16363
|
nav: "MCP",
|
|
16356
16364
|
loading: "Reading MCP servers\u2026",
|
|
16357
16365
|
loadError: "MCP servers are temporarily unavailable.",
|
|
16366
|
+
loadErrorHostMissing: "The host does not expose the mcpManager service (HTTP 404). Usually the plugin host half is not registered or the client/host versions mismatch: confirm the cordis.patch.yml dsh-mcp row, restart dsh web, and hard-refresh (Cmd/Ctrl+Shift+R); after upgrading the plugin, upgrade the host to match.",
|
|
16358
16367
|
retry: "Retry",
|
|
16359
16368
|
empty: "No MCP servers are configured yet.",
|
|
16360
16369
|
addServer: "Add server",
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-side copy for dsh-mcp: the strings the Host renders for operators and
|
|
3
|
+
* the model. The settings UI already routes every client string through the
|
|
4
|
+
* browser locale service (`src/client/locales.ts`); this module gives the
|
|
5
|
+
* host half the same zh/en split, resolved from the same DSH settings
|
|
6
|
+
* document (`locale.preference`), so an English deployment reads English end
|
|
7
|
+
* to end — including the model-facing `mcp_tool_search` description, its
|
|
8
|
+
* search-result text, and the injected `mcp-tool-control` system prompt.
|
|
9
|
+
*
|
|
10
|
+
* Lookup is template-based: `{name}` placeholders are substituted from the
|
|
11
|
+
* caller's vars object. Unknown keys and any settings-read failure fall back
|
|
12
|
+
* to Simplified Chinese, which matches the pre-i18n behavior.
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Simplified Chinese host dictionary (source of truth). */
|
|
17
|
+
const zh = {
|
|
18
|
+
// lib/index.js — model-facing copy.
|
|
19
|
+
toolSearchDesc: "按关键词检索当前启用的 MCP 工具,返回匹配工具的名称/描述/参数 schema,并将命中的工具加入热注入集(下一轮模型请求即可直接调用)。需要某个 MCP 工具但不知道确切名称时使用。",
|
|
20
|
+
toolSearchQuery: "检索关键词,如 gitlab merge request、feishu 文档、hive 表等",
|
|
21
|
+
toolSearchLimit: "最多返回条数,默认 8,最大 20",
|
|
22
|
+
searchHit: "匹配 MCP 工具(已热启用,下一轮可直接调用):\n{lines}",
|
|
23
|
+
searchMiss: "未找到匹配的 MCP 工具,可换关键词重试。",
|
|
24
|
+
toolControlPrompt: "MCP 工具按需可用:需要某个 MCP 工具时先调用 mcp_tool_search(query) 检索,命中后该工具会自动注入当前对话;当前可用 MCP 服务器:{servers}。",
|
|
25
|
+
noServers: "(无)",
|
|
26
|
+
// lib/oauth.js — operator-facing OAuth copy.
|
|
27
|
+
oauthNeedLink: "服务器 \"{serverId}\" 需要 OAuth 授权,请在浏览器打开以下链接完成授权后重试(也可在 Settings → MCP 点击「测试连接」):\n{url}",
|
|
28
|
+
oauthFailPage: "授权失败:{error},可关闭此页面并返回 DSH。",
|
|
29
|
+
oauthOkPage: "授权成功,可关闭此页面并返回 DSH。",
|
|
30
|
+
oauthExchangeFail: "OAuth 授权失败:{error}",
|
|
31
|
+
// lib/mcp-client.js — operator-facing tool-call copy.
|
|
32
|
+
oauthToolLink: "服务器 \"{serverName}\" 需要 OAuth 授权,请在浏览器打开以下链接完成授权后重试:\n{url}",
|
|
33
|
+
oauthToolFallback: "服务器 \"{serverName}\" 需要 OAuth 授权:请在 Settings → MCP 编辑该服务器并点击「测试连接」完成授权后重试",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** English host dictionary; every zh key must exist here. */
|
|
37
|
+
const en = {
|
|
38
|
+
toolSearchDesc: "Search the enabled MCP tools by keyword and return matching tool names/descriptions/parameter schemas; matches are hot-injected for the next model request. Use when you need an MCP tool but do not know its exact name.",
|
|
39
|
+
toolSearchQuery: "Search keywords, e.g. gitlab merge request, feishu document, hive table",
|
|
40
|
+
toolSearchLimit: "Maximum number of results; default 8, max 20",
|
|
41
|
+
searchHit: "Matching MCP tools (hot-enabled, callable in the next request):\n{lines}",
|
|
42
|
+
searchMiss: "No matching MCP tools found; try different keywords.",
|
|
43
|
+
toolControlPrompt: "MCP tools are available on demand: when you need an MCP tool, first call mcp_tool_search(query); a hit is hot-injected into the current conversation. Enabled MCP servers: {servers}.",
|
|
44
|
+
noServers: "(none)",
|
|
45
|
+
oauthNeedLink: "Server \"{serverId}\" requires OAuth authorization — open the link below to authorize and retry (or edit the server in Settings → MCP and click \"Test connection\"):\n{url}",
|
|
46
|
+
oauthFailPage: "Authorization failed: {error}. You can close this page and return to DSH.",
|
|
47
|
+
oauthOkPage: "Authorization succeeded. You can close this page and return to DSH.",
|
|
48
|
+
oauthExchangeFail: "OAuth authorization failed: {error}",
|
|
49
|
+
oauthToolLink: "Server \"{serverName}\" requires OAuth authorization — open the link below to authorize and retry:\n{url}",
|
|
50
|
+
oauthToolFallback: "Server \"{serverName}\" requires OAuth authorization: edit the server in Settings → MCP and click \"Test connection\" to complete authorization",
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** Key table of every host copy key. */
|
|
54
|
+
const KEYS = Object.keys(zh);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Substitute `{name}` placeholders with the given vars.
|
|
58
|
+
* @param template - template text containing `{name}` tokens.
|
|
59
|
+
* @param vars - placeholder values; unknown tokens are left verbatim.
|
|
60
|
+
* @returns the rendered string.
|
|
61
|
+
*/
|
|
62
|
+
function render(template, vars) {
|
|
63
|
+
if (!vars) return template;
|
|
64
|
+
return template.replace(/\{([A-Za-z0-9_]+)\}/g, (match, name) => {
|
|
65
|
+
const value = vars[name];
|
|
66
|
+
return value === void 0 || value === null ? match : String(value);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the DSH locale preference from the Host settings document.
|
|
72
|
+
* @param ctx - optional Host context whose `settings` service owns the
|
|
73
|
+
* `locale` section; when absent or unreadable, falls back to zh.
|
|
74
|
+
* @returns `'zh'` or `'en'`.
|
|
75
|
+
*/
|
|
76
|
+
export function hostLocale(ctx) {
|
|
77
|
+
try {
|
|
78
|
+
const settings = ctx && typeof ctx.get === "function" ? ctx.get("settings") : void 0;
|
|
79
|
+
if (settings && typeof settings.get === "function") {
|
|
80
|
+
const section = settings.get("locale");
|
|
81
|
+
const preference = section && typeof section === "object" ? section.preference : void 0;
|
|
82
|
+
if (preference === "en" || preference === "zh") return preference;
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
// settings unavailable (tests, headless, provider detached): keep zh.
|
|
86
|
+
}
|
|
87
|
+
return "zh";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Render one host copy string for the current DSH locale.
|
|
92
|
+
* @param ctx - Host context used to resolve the locale preference.
|
|
93
|
+
* @param key - copy key (see KEYS).
|
|
94
|
+
* @param vars - optional `{name}` placeholder values.
|
|
95
|
+
* @returns the localized string; unknown keys fall back to the zh template.
|
|
96
|
+
*/
|
|
97
|
+
export function hostText(ctx, key, vars) {
|
|
98
|
+
const locale = hostLocale(ctx);
|
|
99
|
+
const table = locale === "en" ? en : zh;
|
|
100
|
+
const template = table[key] ?? zh[key] ?? "";
|
|
101
|
+
return render(template, vars);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export { en, zh };
|
|
105
|
+
export const __hostLocaleKeys = KEYS;
|
package/lib/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import * as mcpClient from "./mcp-client.js";
|
|
|
5
5
|
import { createOAuthProvider } from "./oauth.js";
|
|
6
6
|
import { probeConnection } from "./probe.js";
|
|
7
7
|
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
8
|
+
import { hostText } from "./host-locales.js";
|
|
8
9
|
import z from "@deepseek-ai/schemastery";
|
|
9
10
|
import { z as z$1 } from "zod";
|
|
10
11
|
import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
|
|
@@ -605,6 +606,21 @@ let McpManagerService = (() => {
|
|
|
605
606
|
};
|
|
606
607
|
}
|
|
607
608
|
/**
|
|
609
|
+
* Whether one resolved client config needs an OAuth provider. Only
|
|
610
|
+
* streamable-http servers WITHOUT a static `Authorization` header do:
|
|
611
|
+
* a static bearer token means the server authenticates directly, and
|
|
612
|
+
* attaching an OAuth provider would make the SDK treat any 401 as an
|
|
613
|
+
* authorization-code challenge — opening the browser / blocking tool
|
|
614
|
+
* calls for servers that never intended OAuth. stdio never needs it.
|
|
615
|
+
* @param config - the resolved client config from {@link toClientConfig}.
|
|
616
|
+
* @returns true when OAuth (authorization-code + PKCE) should be enabled.
|
|
617
|
+
*/
|
|
618
|
+
wantsOAuthProvider(config) {
|
|
619
|
+
if (config.transport !== "streamable-http") return false;
|
|
620
|
+
const names = Object.keys(config.headers ?? {});
|
|
621
|
+
return !names.some((name) => name.toLowerCase() === "authorization");
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
608
624
|
* Mount one server's mcp-client instance without awaiting activation, so a
|
|
609
625
|
* hung server cannot block a Remote call. The mount entry's phase flips via
|
|
610
626
|
* the fiber settlement callbacks.
|
|
@@ -623,14 +639,18 @@ let McpManagerService = (() => {
|
|
|
623
639
|
try {
|
|
624
640
|
const env = await this.resolveEnv(row);
|
|
625
641
|
const globalEnv = await this.resolveGlobalEnv();
|
|
626
|
-
|
|
627
|
-
// OAuth (authorization-code flow) trigger browser
|
|
628
|
-
// connect (unless allowBrowserOnMount is off, the
|
|
629
|
-
//
|
|
630
|
-
//
|
|
631
|
-
//
|
|
632
|
-
|
|
633
|
-
|
|
642
|
+
const config = this.toClientConfig(row, env, globalEnv);
|
|
643
|
+
// OAuth servers (authorization-code flow) trigger browser
|
|
644
|
+
// auth on connect (unless allowBrowserOnMount is off, the
|
|
645
|
+
// default); static-credential servers get no provider so
|
|
646
|
+
// their 401s surface as authentication failures instead of
|
|
647
|
+
// an OAuth flow the server never asked for. Tokens are
|
|
648
|
+
// keyed by serverName so a test-connection authorization
|
|
649
|
+
// is reused by the mount.
|
|
650
|
+
const authProvider = this.wantsOAuthProvider(config)
|
|
651
|
+
? createOAuthProvider(row.serverName, this.ctx, { allowBrowser: this.allowBrowserOnMount, serverUrl: row.url })
|
|
652
|
+
: void 0;
|
|
653
|
+
handle = this.ctx.plugin(MCP_CLIENT_PLUGIN, { ...config, ...authProvider === void 0 ? {} : { authProvider } });
|
|
634
654
|
} catch (error) {
|
|
635
655
|
entry.phase = "failed";
|
|
636
656
|
entry.error = errorText(error);
|
|
@@ -1047,9 +1067,18 @@ let McpManagerService = (() => {
|
|
|
1047
1067
|
// opens the browser for authorization and the probe continues after
|
|
1048
1068
|
// tokens are stored (keyed by serverName, matching the mount path).
|
|
1049
1069
|
const globalEnv = await this.resolveGlobalEnv();
|
|
1050
|
-
const
|
|
1070
|
+
const probeConfig = this.toClientConfig(request.server, env, globalEnv);
|
|
1071
|
+
// Test connection also supports OAuth: an OAuth-protected server
|
|
1072
|
+
// opens the browser for authorization and the probe continues after
|
|
1073
|
+
// tokens are stored (keyed by serverName, matching the mount path).
|
|
1074
|
+
// Static-credential servers (configured Authorization header) skip
|
|
1075
|
+
// the provider so a 401 probe reports the authentication failure
|
|
1076
|
+
// instead of starting an OAuth flow the server never asked for.
|
|
1077
|
+
const probe = await probeConnection(probeConfig, {
|
|
1051
1078
|
timeoutMs: this.probeTimeoutMs,
|
|
1052
|
-
|
|
1079
|
+
...(this.wantsOAuthProvider(probeConfig)
|
|
1080
|
+
? { authProvider: createOAuthProvider(request.server.serverName, this.ctx, { serverUrl: request.server.url }) }
|
|
1081
|
+
: {})
|
|
1053
1082
|
});
|
|
1054
1083
|
const elapsedMs = Date.now() - startedAt;
|
|
1055
1084
|
return {
|
|
@@ -1106,12 +1135,12 @@ let McpManagerService = (() => {
|
|
|
1106
1135
|
installToolControl() {
|
|
1107
1136
|
this.ctx.tools.register({
|
|
1108
1137
|
name: "mcp_tool_search",
|
|
1109
|
-
description:
|
|
1138
|
+
description: hostText(this.ctx, "toolSearchDesc"),
|
|
1110
1139
|
parameters: {
|
|
1111
1140
|
type: "object",
|
|
1112
1141
|
properties: {
|
|
1113
|
-
query: { type: "string", description:
|
|
1114
|
-
limit: { type: "number", description:
|
|
1142
|
+
query: { type: "string", description: hostText(this.ctx, "toolSearchQuery") },
|
|
1143
|
+
limit: { type: "number", description: hostText(this.ctx, "toolSearchLimit") }
|
|
1115
1144
|
},
|
|
1116
1145
|
required: ["query"],
|
|
1117
1146
|
additionalProperties: false
|
|
@@ -1151,8 +1180,8 @@ let McpManagerService = (() => {
|
|
|
1151
1180
|
content: [{
|
|
1152
1181
|
type: "text",
|
|
1153
1182
|
text: lines.length > 0
|
|
1154
|
-
?
|
|
1155
|
-
:
|
|
1183
|
+
? hostText(this.ctx, "searchHit", { lines: lines.join("\n") })
|
|
1184
|
+
: hostText(this.ctx, "searchMiss")
|
|
1156
1185
|
}]
|
|
1157
1186
|
};
|
|
1158
1187
|
}
|
|
@@ -1178,7 +1207,7 @@ let McpManagerService = (() => {
|
|
|
1178
1207
|
assembly.sections = (assembly.sections || []).filter((section) => section.name !== "mcp-tool-control");
|
|
1179
1208
|
assembly.sections.push({
|
|
1180
1209
|
name: "mcp-tool-control",
|
|
1181
|
-
text:
|
|
1210
|
+
text: hostText(this.ctx, "toolControlPrompt", { servers: servers.join(", ") || hostText(this.ctx, "noServers") })
|
|
1182
1211
|
});
|
|
1183
1212
|
}
|
|
1184
1213
|
assembly.tools = kept;
|
package/lib/mcp-client.js
CHANGED
|
@@ -10,7 +10,8 @@ import { isDeepStrictEqual } from "node:util";
|
|
|
10
10
|
import { z as z$1 } from "zod";
|
|
11
11
|
import { isImageAdmissionError } from "@deepseek-ai/dsh-attachment";
|
|
12
12
|
import { assertSupportedJsonSchema } from "@deepseek-ai/dsh-tools";
|
|
13
|
-
import { createToolAuthorizationUrl } from "./oauth.js";
|
|
13
|
+
import { createToolAuthorizationUrl, OAUTH_REQUIRED_CODE, oauthRequiredError } from "./oauth.js";
|
|
14
|
+
import { hostText } from "./host-locales.js";
|
|
14
15
|
//#region lib/types/transport.js
|
|
15
16
|
/**
|
|
16
17
|
* Transport factory: creates the appropriate MCP transport based on the
|
|
@@ -309,10 +310,15 @@ function createExecutor(client, ctx, rawName, taskRequired, opts, projections) {
|
|
|
309
310
|
if (expired) {
|
|
310
311
|
try {
|
|
311
312
|
const authUrl = await createToolAuthorizationUrl(opts.serverName, ctx, opts.url);
|
|
312
|
-
throw
|
|
313
|
+
throw oauthRequiredError(hostText(ctx, "oauthToolLink", { serverName: opts.serverName, url: authUrl }));
|
|
313
314
|
} catch (error) {
|
|
314
|
-
|
|
315
|
-
|
|
315
|
+
// The link-carrying error thrown just above is the actionable
|
|
316
|
+
// outcome; anything else (discovery/registration failure) is
|
|
317
|
+
// reported as the generic Settings → MCP fallback. Keyed on
|
|
318
|
+
// the stable error code so localized message text never
|
|
319
|
+
// drives control flow.
|
|
320
|
+
if (error instanceof Error && error.code === OAUTH_REQUIRED_CODE) throw error;
|
|
321
|
+
throw oauthRequiredError(new Error(hostText(ctx, "oauthToolFallback", { serverName: opts.serverName })));
|
|
316
322
|
}
|
|
317
323
|
}
|
|
318
324
|
}
|
package/lib/oauth.js
CHANGED
|
@@ -21,6 +21,25 @@ import {
|
|
|
21
21
|
startAuthorization,
|
|
22
22
|
} from "@modelcontextprotocol/sdk/client/auth.js";
|
|
23
23
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
24
|
+
import { hostText } from "./host-locales.js";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Error code marking an "OAuth authorization required" failure. Throwers set
|
|
28
|
+
* `error.code` to this value so control flow (re-throw decisions) keys on a
|
|
29
|
+
* stable code, never on localized message text.
|
|
30
|
+
*/
|
|
31
|
+
export const OAUTH_REQUIRED_CODE = "MCP_OAUTH_REQUIRED";
|
|
32
|
+
|
|
33
|
+
/** Set the OAuth-required marker on an Error and return it. */
|
|
34
|
+
function oauthRequired(error) {
|
|
35
|
+
error.code = OAUTH_REQUIRED_CODE;
|
|
36
|
+
return error;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Build a localized "authorization required" Error carrying the stable code. */
|
|
40
|
+
export function oauthRequiredError(message) {
|
|
41
|
+
return oauthRequired(new Error(message));
|
|
42
|
+
}
|
|
24
43
|
|
|
25
44
|
/** Credential reference namespace prefix for per-server OAuth tokens. */
|
|
26
45
|
const OAUTH_REF_PREFIX = "DSH_MCP_OAUTH_";
|
|
@@ -253,7 +272,7 @@ export function createOAuthProvider(serverId, ctx, options = {}) {
|
|
|
253
272
|
// fail with the clickable authorization link. The shared
|
|
254
273
|
// listener keeps running so opening the URL completes the flow.
|
|
255
274
|
log.info(`${tag}: mount requires authorization, not opening browser`);
|
|
256
|
-
throw new Error(
|
|
275
|
+
throw oauthRequired(new Error(hostText(ctx, "oauthNeedLink", { serverId, url })));
|
|
257
276
|
}
|
|
258
277
|
log.info(`${tag}: opening browser for authorization: ${url}`);
|
|
259
278
|
openBrowser(url);
|
|
@@ -273,12 +292,12 @@ export function createOAuthProvider(serverId, ctx, options = {}) {
|
|
|
273
292
|
const error = url.searchParams.get("error");
|
|
274
293
|
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
275
294
|
if (error) {
|
|
276
|
-
res.end(
|
|
295
|
+
res.end(hostText(ctx, "oauthFailPage", { error }));
|
|
277
296
|
log.error(`${tag}: callback rejected: ${error}`);
|
|
278
297
|
resolveResult({ error });
|
|
279
298
|
return;
|
|
280
299
|
}
|
|
281
|
-
res.end(
|
|
300
|
+
res.end(hostText(ctx, "oauthOkPage"));
|
|
282
301
|
log.info(`${tag}: authorization callback received (code)`);
|
|
283
302
|
resolveResult({ code });
|
|
284
303
|
});
|
|
@@ -291,7 +310,7 @@ export function createOAuthProvider(serverId, ctx, options = {}) {
|
|
|
291
310
|
openBrowser(authorizationUrl.toString());
|
|
292
311
|
try {
|
|
293
312
|
const result = await resultPromise;
|
|
294
|
-
if (result.error) throw new Error(
|
|
313
|
+
if (result.error) throw oauthRequired(new Error(hostText(ctx, "oauthExchangeFail", { error: result.error })));
|
|
295
314
|
const authServerUrl = new URL(authorizationUrl.origin);
|
|
296
315
|
log.info(`${tag}: discovering authorization server metadata at ${authServerUrl}`);
|
|
297
316
|
const metadata = await discoverAuthorizationServerMetadata(authServerUrl);
|
|
@@ -392,13 +411,13 @@ export async function createToolAuthorizationUrl(serverId, ctx, serverUrl) {
|
|
|
392
411
|
const error = callbackUrl.searchParams.get("error");
|
|
393
412
|
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
394
413
|
if (error) {
|
|
395
|
-
res.end(
|
|
414
|
+
res.end(hostText(ctx, "oauthFailPage", { error }));
|
|
396
415
|
log.error(`${tag}: callback rejected: ${error}`);
|
|
397
416
|
resolve();
|
|
398
417
|
server.close();
|
|
399
418
|
return;
|
|
400
419
|
}
|
|
401
|
-
res.end(
|
|
420
|
+
res.end(hostText(ctx, "oauthOkPage"));
|
|
402
421
|
log.info(`${tag}: tool-auth callback received (code)`);
|
|
403
422
|
try {
|
|
404
423
|
const authServerUrl = new URL(authorizationUrl.origin);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.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",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
24
|
"build": "node scripts/build.mjs",
|
|
25
|
-
"test": "node --test test/mcp-image-projection.test.mjs"
|
|
25
|
+
"test": "node --test test/mcp-image-projection.test.mjs test/host-locales.test.mjs"
|
|
26
26
|
},
|
|
27
27
|
"author": "Arvin.qi <arvin.qi@qq.com>",
|
|
28
28
|
"license": "MIT",
|
|
@@ -37,7 +37,6 @@
|
|
|
37
37
|
"dsh": {
|
|
38
38
|
"client": {
|
|
39
39
|
"inject": [
|
|
40
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
41
40
|
"@deepseek-ai/dsh-api-remotes",
|
|
42
41
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
43
42
|
"@deepseek-ai/dsh-client-locale"
|
package/scripts/build.mjs
CHANGED
|
@@ -50,12 +50,9 @@ const EXTERNALS = [
|
|
|
50
50
|
'react-dom',
|
|
51
51
|
'react-dom/client',
|
|
52
52
|
'@deepseek-ai/cordis',
|
|
53
|
+
'@deepseek-ai/dsh-client-store',
|
|
53
54
|
'@deepseek-ai/dsh-client-ui-slots',
|
|
54
|
-
'@deepseek-ai/dsh-client-web-react',
|
|
55
55
|
'@deepseek-ai/dsh-client-ui-primitives',
|
|
56
|
-
'@deepseek-ai/dsh-client-ui-attachment',
|
|
57
|
-
'@deepseek-ai/dsh-client-schema-form',
|
|
58
|
-
'@deepseek-ai/dsh-client-runtime/client',
|
|
59
56
|
]
|
|
60
57
|
|
|
61
58
|
/** Locate the esbuild package inside a pnpm checkout (store or hoisted). */
|
|
@@ -79,6 +79,7 @@ export function McpSettingsSection(props: McpSettingsSectionProps): ReactNode {
|
|
|
79
79
|
const t = props.t
|
|
80
80
|
|
|
81
81
|
const [loadErrorDetail, setLoadErrorDetail] = useState<string | null>(null)
|
|
82
|
+
const [hostMissing, setHostMissing] = useState(false)
|
|
82
83
|
const [tools, setTools] = useState<McpToolsState | null>(null)
|
|
83
84
|
const [toolsError, setToolsError] = useState<string | null>(null)
|
|
84
85
|
const [expanded, setExpanded] = useState<ReadonlySet<string>>(new Set())
|
|
@@ -107,7 +108,12 @@ export function McpSettingsSection(props: McpSettingsSectionProps): ReactNode {
|
|
|
107
108
|
// The settings shell hides Remote failures behind a generic copy; surface
|
|
108
109
|
// the real message here so a broken list() is diagnosable from the page.
|
|
109
110
|
console.error('[dsh-mcp] list failed:', error)
|
|
110
|
-
|
|
111
|
+
const message = String((error instanceof Error ? error.message : error) ?? error)
|
|
112
|
+
setLoadErrorDetail(message)
|
|
113
|
+
// HTTP 404 on /api/mcpManager/* means the host did not register the
|
|
114
|
+
// mcpManager Typert service (host half missing or client/host version
|
|
115
|
+
// mismatch). Surface that distinctively instead of a bare transport error.
|
|
116
|
+
setHostMissing(/HTTP 404|transport failure/.test(message))
|
|
111
117
|
setLoadState('error')
|
|
112
118
|
}
|
|
113
119
|
|
|
@@ -117,6 +123,7 @@ export function McpSettingsSection(props: McpSettingsSectionProps): ReactNode {
|
|
|
117
123
|
setServers(servers)
|
|
118
124
|
setLoadState('ready')
|
|
119
125
|
setLoadErrorDetail(null)
|
|
126
|
+
setHostMissing(false)
|
|
120
127
|
},
|
|
121
128
|
(error) => fail(error),
|
|
122
129
|
)
|
|
@@ -131,6 +138,7 @@ export function McpSettingsSection(props: McpSettingsSectionProps): ReactNode {
|
|
|
131
138
|
setServers(servers)
|
|
132
139
|
setLoadState('ready')
|
|
133
140
|
setLoadErrorDetail(null)
|
|
141
|
+
setHostMissing(false)
|
|
134
142
|
},
|
|
135
143
|
(error) => { if (current) fail(error) },
|
|
136
144
|
)
|
|
@@ -169,7 +177,7 @@ export function McpSettingsSection(props: McpSettingsSectionProps): ReactNode {
|
|
|
169
177
|
setRefreshing(prev => new Set(prev).add(serverName))
|
|
170
178
|
void Promise.all([
|
|
171
179
|
list().then(
|
|
172
|
-
(servers) => { setServers(servers); setLoadState('ready') },
|
|
180
|
+
(servers) => { setServers(servers); setLoadState('ready'); setHostMissing(false) },
|
|
173
181
|
() => {},
|
|
174
182
|
),
|
|
175
183
|
props.toolsList().then(setTools, () => {}),
|
|
@@ -225,6 +233,7 @@ export function McpSettingsSection(props: McpSettingsSectionProps): ReactNode {
|
|
|
225
233
|
return (
|
|
226
234
|
<div className={css.failure}>
|
|
227
235
|
<p role="alert">{t('loadError')}</p>
|
|
236
|
+
{hostMissing ? <p className={css.muted}>{t('loadErrorHostMissing')}</p> : null}
|
|
228
237
|
{loadErrorDetail !== null ? <pre className={css.muted}>{loadErrorDetail}</pre> : null}
|
|
229
238
|
<button type="button" onClick={() => { setLoadState('loading'); load() }}>{t('retry')}</button>
|
|
230
239
|
</div>
|
package/src/client/index.ts
CHANGED
|
@@ -95,7 +95,7 @@ function failureOf(error: { code: string; message: string }): McpManagerFailure
|
|
|
95
95
|
* itself so it needs no modification to any in-box package.
|
|
96
96
|
* @param ctx - Client Cordis root.
|
|
97
97
|
*/
|
|
98
|
-
export async function apply(ctx:
|
|
98
|
+
export async function apply(ctx: Context): Promise<void> {
|
|
99
99
|
await ctx.remote.$mount(remoteContribution)
|
|
100
100
|
|
|
101
101
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-settings-mcp: dictionaries')
|
package/src/client/locales.ts
CHANGED
|
@@ -5,6 +5,7 @@ export const zh = {
|
|
|
5
5
|
nav: 'MCP',
|
|
6
6
|
loading: '正在读取 MCP 服务器…',
|
|
7
7
|
loadError: '暂时无法读取 MCP 服务器。',
|
|
8
|
+
loadErrorHostMissing: '宿主端未提供 mcpManager 服务(HTTP 404)。通常是插件 host 半未注册或 client/host 版本不匹配:请确认 cordis.patch.yml 已注册 dsh-mcp 行、重启 dsh web 并硬刷新(Cmd/Ctrl+Shift+R);若升级过插件请同步升级宿主。',
|
|
8
9
|
retry: '重试',
|
|
9
10
|
empty: '还没有配置 MCP 服务器。',
|
|
10
11
|
addServer: '添加服务器',
|
|
@@ -111,6 +112,7 @@ export const en = {
|
|
|
111
112
|
nav: 'MCP',
|
|
112
113
|
loading: 'Reading MCP servers…',
|
|
113
114
|
loadError: 'MCP servers are temporarily unavailable.',
|
|
115
|
+
loadErrorHostMissing: 'The host does not expose the mcpManager service (HTTP 404). Usually the plugin host half is not registered or the client/host versions mismatch: confirm the cordis.patch.yml dsh-mcp row, restart dsh web, and hard-refresh (Cmd/Ctrl+Shift+R); after upgrading the plugin, upgrade the host to match.',
|
|
114
116
|
retry: 'Retry',
|
|
115
117
|
empty: 'No MCP servers are configured yet.',
|
|
116
118
|
addServer: 'Add server',
|
package/src/client/mcp-store.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* inject callbacks; the store only mirrors their results so components stay
|
|
5
5
|
* pure presentational.
|
|
6
6
|
*/
|
|
7
|
-
import { defineStore, type EngineStoreHandle } from '@deepseek-ai/dsh-client-
|
|
7
|
+
import { defineStore, type EngineStoreHandle } from '@deepseek-ai/dsh-client-store'
|
|
8
8
|
import type {
|
|
9
9
|
McpProbeView, McpServerId, McpServerInput, McpServerView, McpTransportKind,
|
|
10
10
|
} from './types.ts'
|