dsh-mcp 1.11.0 → 1.11.1
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 +13 -0
- package/CHANGELOG.md +13 -0
- package/README.en.md +6 -0
- package/README.md +9 -0
- package/lib/client.js +35 -4
- package/lib/index.js +92 -28
- package/lib/oauth.js +13 -0
- package/package.json +2 -1
- package/src/client/McpSettingsSection.tsx +1 -0
- package/src/client/ServerForm.tsx +10 -0
- package/src/client/ServersJsonEditor.tsx +2 -0
- package/src/client/locales.ts +4 -0
- package/src/client/mcp-store.ts +7 -0
- package/src/client/remote-contribution.js +7 -0
- package/src/client/types.ts +4 -0
package/CHANGELOG.en.md
CHANGED
|
@@ -7,6 +7,19 @@ 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
|
+
## Compatibility
|
|
11
|
+
|
|
12
|
+
- **Supported DSH version**: **dsh `0.1.5-rc.1`** — developed and verified on `0.1.5-rc.1` (declared in `package.json` → `dsh.supported`). When the DSH and plugin versions do not match, the Settings page reports a diagnosis (check the `cordis.patch.yml` row → restart `dsh web` → hard-refresh → upgrade both sides).
|
|
13
|
+
- **Release convention**: every version entry states `- **Supported DSH version**: dsh <version>`, mirrored in the GitHub Release notes.
|
|
14
|
+
|
|
15
|
+
## [1.11.1] - 2026-09-13
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **OAuth is now an explicit switch instead of a header guess (no more repeated browser authorizations)**: the previous rule was "streamable-http without an `Authorization` header gets an OAuth provider", so servers that authenticate with static tokens or custom headers (`x-bbzai-mcp-token`, `X-Mcp-Token`, `Private-Token`) were treated as OAuth servers. When such a server answered 401 (a stale token, a gateway challenge), the SDK asked for authorization, the plugin opened the browser and started its loopback callback (`127.0.0.1:<port>/callback`) — a flow that could never satisfy those servers, so every reconnect or remount opened the browser again. A stored server now has an **`oauth: true`** switch (the form's "Use OAuth authorization" checkbox, off by default; taking over a declaration without an `Authorization` header ticks it, and it stays editable)
|
|
20
|
+
- **Upgrade migration**: servers that already hold OAuth credentials (a token or a client registration) keep `oauth: true`, everything else is turned off — so servers that genuinely need OAuth keep working while the misdetected ones stop opening the browser
|
|
21
|
+
- **Automatic-authorization circuit breaker**: the plugin opens the browser **at most once per server per process**; later automatic attempts return the authorization-link error instead (the 1.8.0 behaviour), and "Test connection" can still start an authorization on demand
|
|
22
|
+
|
|
10
23
|
## [1.11.0] - 2026-09-13
|
|
11
24
|
|
|
12
25
|
### Added
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@
|
|
|
7
7
|
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
|
|
8
8
|
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
9
9
|
|
|
10
|
+
## 兼容性
|
|
11
|
+
|
|
12
|
+
- **支持版本**:**dsh `0.1.5-rc.1`** —— 本插件在 `0.1.5-rc.1` 上开发与验证(`package.json` → `dsh.supported` 同步声明)。DSH 与插件版本不匹配时,设置页会给出排查诊断(核对 `cordis.patch.yml` 注册行 → 重启 `dsh web` → 硬刷新 → 同步升级)。
|
|
13
|
+
- **发版约定**:每个版本条目都要声明 `- **支持版本**:dsh <版本>`,并在 GitHub Release notes 中同步。
|
|
14
|
+
|
|
15
|
+
## [1.11.1] - 2026-09-13
|
|
16
|
+
|
|
17
|
+
### 修复
|
|
18
|
+
|
|
19
|
+
- **OAuth 改为显式开关,不再凭请求头猜测(停止反复弹出浏览器授权)**:此前规则是"streamable-http 且没有 `Authorization` 头 → 挂 OAuth provider",于是用静态 token/自定义头(`x-bbzai-mcp-token`、`X-Mcp-Token`、`Private-Token`)认证的服务器也被当成 OAuth 服务器。这类服务器返回 401 时(token 失效、网关挑战等),SDK 会要求授权 → 插件打开浏览器并起回环回调(`127.0.0.1:<port>/callback`),而该流程永远无法让它们通过 → 每次重连/重挂载就再弹一次。现在服务器配置新增 **`oauth: true`** 开关(设置页勾选「使用 OAuth 授权」,默认关;接管声明时若该声明没有 `Authorization` 头则默认勾上,之后仍可修改)
|
|
20
|
+
- **升级自动迁移**:已存在 OAuth 凭据(token 或 client 注册记录)的服务器会保留 `oauth: true`,其余一律关闭——因此**现有真正需要 OAuth 的服务器不受影响,误判的那批立刻停止弹窗**
|
|
21
|
+
- **自动弹窗熔断**:同一服务器**每个进程最多自动打开一次**浏览器授权;之后的自动尝试不再弹窗,改为返回带授权链接的错误(沿用 1.8.0 的能力),手动点「测试连接」仍可随时发起授权
|
|
22
|
+
|
|
10
23
|
## [1.11.0] - 2026-09-13
|
|
11
24
|
|
|
12
25
|
### 新增
|
package/README.en.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://dshfind.com/en/plugins/ArvinQi/dsh-mcp?ref=badge)
|
|
4
4
|
|
|
5
|
+
> **Supported DSH version**: `dsh 0.1.5-rc.1` — developed and verified on `0.1.5-rc.1`, declared in `package.json` → `dsh.supported`. When the DSH and plugin versions do not match, the Settings page shows a diagnosis (check the registration row → restart `dsh web` → hard-refresh → upgrade both sides).
|
|
6
|
+
|
|
5
7
|

|
|
6
8
|
|
|
7
9
|
## Why dsh-mcp?
|
|
@@ -78,6 +80,7 @@ Since 1.11.0 those declarations appear in Settings → MCP, badged "cordis decla
|
|
|
78
80
|
- **Imported into storages as mirrors**: at startup and on every refresh the declarations are imported into the storage domain (id `cordis:<rowId>`, with `origin/declaredIn/declaredRowId`), **import only** — a row created in the plugin is never overwritten; a mirror whose declaration disappeared is **removed automatically** (a mirror is only a copy of a declaration, so an ownerless one must not pile up); a declaration containing a `!!js` expression is skipped and flagged with the reason (its value only resolves inside the Loader). Mirrors are never mounted — the composition owns the mount.
|
|
79
81
|
- **Take over / give back**: clicking "Take over" makes the plugin write an id-targeted `disabled: true` into its own **managed block** in `cordis.patch.yml` (a `.dsh-mcp.bak` backup is taken before the first write; atomic, idempotent, reversible, confined to that block). The declaration releases the `serverName` and the plugin mounts its own row instead — enabling **OAuth authorization, managed credentials, `${VAR}` header substitution and connection tests**. A hard mount failure rolls back automatically (the block is removed and the row returns to mirror state) and reports why; "Give back" hands the mount back to the declaration and deletes the managed row. A declaration without an explicit `id`, or one containing a `!!js` expression, cannot be taken over.
|
|
80
82
|
- **OAuth / placeholder limit (`needsPlugin`)**: a declaration can carry neither an OAuth provider nor `${VAR}` / bare-name placeholder substitution — only the plugin does those. Such a declaration can therefore only work while the plugin owns the mount: the page reports it as **failed with the reason** (not "connecting"), "Give back" makes its tools disappear (the confirmation and the result warning both say so), and taking it over again restores them.
|
|
83
|
+
- **OAuth is an explicit switch (since 1.11.1)**: an OAuth provider is attached only when the server sets `oauth: true` (the form's "Use OAuth authorization" checkbox, off by default). **Do not tick it for servers that authenticate with static tokens or custom headers** — otherwise a 401 is treated as an OAuth challenge and the plugin keeps opening the browser (with its `127.0.0.1:<port>/callback` loopback listener). The plugin also opens the browser **at most once per server per process**; later automatic attempts return the authorization-link error, and "Test connection" can still start one on demand. Upgrading migrates automatically from existing OAuth credentials, so servers that genuinely need OAuth are unaffected.
|
|
81
84
|
- **Safe degradation when the hot reload does not commit**: if the native tools have not unregistered within 5 seconds of writing the disable block (that profile's patch hot reload did not commit — a sibling entry failing its re-apply rolls the whole generation back), the plugin **registers the takeover without mounting** (the managed row is marked `pendingTakeover`); the next dsh start mounts it, and "Give back" reports the same need for a restart. A mount is also always skipped when same-named `mcp__<server>__` tools already exist without a local mount, so the plugin never fights a still-mounted declaration for one name.
|
|
82
85
|
- **Tool search covers them**: search/hot injection and per-tool switches work by `mcp__` prefix across the whole tool set, so declared tools need no extra configuration.
|
|
83
86
|
|
|
@@ -296,6 +299,9 @@ Check in order:
|
|
|
296
299
|
reinstalling (link install).
|
|
297
300
|
- Configuration changes (bundle additions/removals, new plugin rows) require restarting `dsh web` to enter
|
|
298
301
|
the client roster.
|
|
302
|
+
- **Every release must declare its supported DSH version**: add a
|
|
303
|
+
`- **Supported DSH version**: dsh <version>` line to the CHANGELOG entry and the GitHub Release notes, and
|
|
304
|
+
update `package.json` → `dsh.supported` plus the "Supported DSH version" note at the top of this README.
|
|
299
305
|
|
|
300
306
|
## Changelog
|
|
301
307
|
|
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://dshfind.com/zh/plugins/ArvinQi/dsh-mcp?ref=badge)
|
|
6
6
|
|
|
7
|
+
> **支持版本**:`dsh 0.1.5-rc.1` —— 本插件在 `0.1.5-rc.1` 上开发与验证,`package.json` → `dsh.supported` 同步声明。DSH 与插件两侧版本不匹配时,设置页会给出排查诊断(检查注册行 → 重启 `dsh web` → 硬刷新 → 同步升级)。
|
|
8
|
+
|
|
7
9
|

|
|
8
10
|
|
|
9
11
|
## 为什么用 dsh-mcp?
|
|
@@ -76,6 +78,11 @@ DSH 原生支持在组合里直接声明 MCP 服务器:**一行一台**,`nam
|
|
|
76
78
|
`${VAR}`/裸变量名占位符——这两件事只有插件会做。因此这类声明**只能由插件挂载**:页面会把它们标为
|
|
77
79
|
**挂载失败并说明原因**(不是"连接中"),「释放」会让工具消失(确认框与结果告警都会提示),恢复只需
|
|
78
80
|
重新「接管」。
|
|
81
|
+
- **OAuth 是显式开关(1.11.1 起)**:服务器配置里的 `oauth: true`(设置页「使用 OAuth 授权」勾选,默认关)
|
|
82
|
+
才会挂 OAuth provider。**用静态 token 或自定义头认证的服务器不要勾**——否则它返回 401 时会被当成 OAuth
|
|
83
|
+
挑战,插件会反复打开浏览器授权(并起 `127.0.0.1:<port>/callback` 回环回调)。另外,同一服务器**每个进程
|
|
84
|
+
最多自动弹一次**浏览器,之后只返回带授权链接的错误;手动「测试连接」仍可随时发起授权。升级时会按已有
|
|
85
|
+
OAuth 凭据自动迁移,真正需要 OAuth 的服务器不受影响。
|
|
79
86
|
- **热重载未提交时的安全降级**:写入停用块后若原生工具 5 秒内没有注销(该 profile 的 patch 热重载未提交,
|
|
80
87
|
例如某个兄弟条目重建失败导致整代回滚),插件会**登记接管但暂不挂载**(管理行标记 `pendingTakeover`),
|
|
81
88
|
重启 dsh 后由插件挂载;「释放」在同样情况下会提示需要重启。此外,挂载前若发现同名 `mcp__<server>__`
|
|
@@ -278,5 +285,7 @@ dsh plugin --profile web add link:<本仓库绝对路径>
|
|
|
278
285
|
- 浏览器半部改 `src/client/*` 后重新 `node scripts/build.mjs`;host 半部改动无需重装
|
|
279
286
|
(link 安装直接生效)。
|
|
280
287
|
- 配置变更(bundles 增删、新插件行)需重启 `dsh web` 才进入 client roster。
|
|
288
|
+
- **每次发版都必须声明支持的 DSH 版本**:在 CHANGELOG 对应条目与 GitHub Release notes 里加一行
|
|
289
|
+
`- **支持版本**:dsh <版本>`,并同步更新 `package.json` 的 `dsh.supported` 与 README 顶部的「支持版本」。
|
|
281
290
|
|
|
282
291
|
[](https://dshfind.com/zh/plugins/ArvinQi/dsh-mcp?ref=badge)
|
package/lib/client.js
CHANGED
|
@@ -14577,6 +14577,7 @@ var _deepseek_ai_dsh_mcp_manager_mcpManager_list_result$schema = external_export
|
|
|
14577
14577
|
})).readonly(),
|
|
14578
14578
|
"toolCallTimeoutMs": external_exports.number().readonly(),
|
|
14579
14579
|
"failOnStartupError": external_exports.boolean().readonly(),
|
|
14580
|
+
"oauth": external_exports.boolean().readonly().optional(),
|
|
14580
14581
|
"status": external_exports.object({
|
|
14581
14582
|
"phase": external_exports.union([external_exports.literal("failed"), external_exports.literal("stopped"), external_exports.literal("mounting"), external_exports.literal("live")]).readonly(),
|
|
14582
14583
|
"tools": external_exports.array(external_exports.string()).readonly(),
|
|
@@ -14622,7 +14623,8 @@ var _deepseek_ai_dsh_mcp_manager_mcpManager_test_parameter_0$schema = external_e
|
|
|
14622
14623
|
"value": external_exports.string().readonly()
|
|
14623
14624
|
})).readonly(),
|
|
14624
14625
|
"toolCallTimeoutMs": external_exports.number().readonly(),
|
|
14625
|
-
"failOnStartupError": external_exports.boolean().readonly()
|
|
14626
|
+
"failOnStartupError": external_exports.boolean().readonly(),
|
|
14627
|
+
"oauth": external_exports.boolean().readonly().optional()
|
|
14626
14628
|
}).readonly(),
|
|
14627
14629
|
"env": external_exports.array(external_exports.object({
|
|
14628
14630
|
"name": external_exports.string().readonly(),
|
|
@@ -14659,7 +14661,8 @@ var _deepseek_ai_dsh_mcp_manager_mcpManager_upsert_parameter_0$schema = external
|
|
|
14659
14661
|
"value": external_exports.string().readonly()
|
|
14660
14662
|
})).readonly(),
|
|
14661
14663
|
"toolCallTimeoutMs": external_exports.number().readonly(),
|
|
14662
|
-
"failOnStartupError": external_exports.boolean().readonly()
|
|
14664
|
+
"failOnStartupError": external_exports.boolean().readonly(),
|
|
14665
|
+
"oauth": external_exports.boolean().readonly().optional()
|
|
14663
14666
|
}).readonly(),
|
|
14664
14667
|
"env": external_exports.array(external_exports.object({
|
|
14665
14668
|
"name": external_exports.string().readonly(),
|
|
@@ -14690,6 +14693,7 @@ var _deepseek_ai_dsh_mcp_manager_mcpManager_upsert_result$schema = external_expo
|
|
|
14690
14693
|
})).readonly(),
|
|
14691
14694
|
"toolCallTimeoutMs": external_exports.number().readonly(),
|
|
14692
14695
|
"failOnStartupError": external_exports.boolean().readonly(),
|
|
14696
|
+
"oauth": external_exports.boolean().readonly().optional(),
|
|
14693
14697
|
"status": external_exports.object({
|
|
14694
14698
|
"phase": external_exports.union([external_exports.literal("failed"), external_exports.literal("stopped"), external_exports.literal("mounting"), external_exports.literal("live")]).readonly(),
|
|
14695
14699
|
"tools": external_exports.array(external_exports.string()).readonly(),
|
|
@@ -14731,7 +14735,8 @@ var _deepseek_ai_dsh_mcp_manager_mcpManager_upsertJson_parameter_0$schema = exte
|
|
|
14731
14735
|
"value": external_exports.string().readonly()
|
|
14732
14736
|
})).readonly()]).readonly().optional(),
|
|
14733
14737
|
"toolCallTimeoutMs": external_exports.number().readonly().optional(),
|
|
14734
|
-
"failOnStartupError": external_exports.boolean().readonly().optional()
|
|
14738
|
+
"failOnStartupError": external_exports.boolean().readonly().optional(),
|
|
14739
|
+
"oauth": external_exports.boolean().readonly().optional()
|
|
14735
14740
|
}).readonly(),
|
|
14736
14741
|
"env": external_exports.array(external_exports.object({
|
|
14737
14742
|
"name": external_exports.string().readonly(),
|
|
@@ -14767,6 +14772,7 @@ var _deepseek_ai_dsh_mcp_manager_mcpManager_upsertJson_result$schema = external_
|
|
|
14767
14772
|
})).readonly(),
|
|
14768
14773
|
"toolCallTimeoutMs": external_exports.number().readonly(),
|
|
14769
14774
|
"failOnStartupError": external_exports.boolean().readonly(),
|
|
14775
|
+
"oauth": external_exports.boolean().readonly().optional(),
|
|
14770
14776
|
"status": external_exports.object({
|
|
14771
14777
|
"phase": external_exports.union([external_exports.literal("failed"), external_exports.literal("stopped"), external_exports.literal("mounting"), external_exports.literal("live")]).readonly(),
|
|
14772
14778
|
"tools": external_exports.array(external_exports.string()).readonly(),
|
|
@@ -14887,6 +14893,7 @@ var _deepseek_ai_dsh_mcp_manager_mcpManager_adopt_result$schema = external_expor
|
|
|
14887
14893
|
})).readonly(),
|
|
14888
14894
|
"toolCallTimeoutMs": external_exports.number().readonly(),
|
|
14889
14895
|
"failOnStartupError": external_exports.boolean().readonly(),
|
|
14896
|
+
"oauth": external_exports.boolean().readonly().optional(),
|
|
14890
14897
|
"status": external_exports.object({
|
|
14891
14898
|
"phase": external_exports.union([external_exports.literal("failed"), external_exports.literal("stopped"), external_exports.literal("mounting"), external_exports.literal("live")]).readonly(),
|
|
14892
14899
|
"tools": external_exports.array(external_exports.string()).readonly(),
|
|
@@ -15225,7 +15232,10 @@ function emptyDraft() {
|
|
|
15225
15232
|
toolCallTimeoutMs: String(DEFAULT_TOOL_CALL_TIMEOUT_MS),
|
|
15226
15233
|
// Reject the mount by default when the startup connection fails, so a
|
|
15227
15234
|
// broken server never registers stale tools.
|
|
15228
|
-
failOnStartupError: true
|
|
15235
|
+
failOnStartupError: true,
|
|
15236
|
+
// OAuth is opt-in: guessing it from the headers opened the browser for
|
|
15237
|
+
// servers that authenticate with static tokens.
|
|
15238
|
+
oauth: false
|
|
15229
15239
|
};
|
|
15230
15240
|
}
|
|
15231
15241
|
function draftFromServer(server) {
|
|
@@ -15240,6 +15250,7 @@ function draftFromServer(server) {
|
|
|
15240
15250
|
url: server.url,
|
|
15241
15251
|
headersText: server.headers.map((header) => `${header.name}: ${header.value}`).join("\n"),
|
|
15242
15252
|
toolCallTimeoutMs: String(server.toolCallTimeoutMs),
|
|
15253
|
+
oauth: server.oauth === true,
|
|
15243
15254
|
failOnStartupError: server.failOnStartupError
|
|
15244
15255
|
};
|
|
15245
15256
|
}
|
|
@@ -15326,6 +15337,7 @@ function draftToSubmission(draft) {
|
|
|
15326
15337
|
url: draft.url.trim(),
|
|
15327
15338
|
headers: parseHeaders(draft.headersText),
|
|
15328
15339
|
toolCallTimeoutMs: parseTimeout(draft.toolCallTimeoutMs),
|
|
15340
|
+
oauth: draft.oauth,
|
|
15329
15341
|
failOnStartupError: draft.failOnStartupError
|
|
15330
15342
|
}
|
|
15331
15343
|
};
|
|
@@ -15591,6 +15603,18 @@ function McpServerForm(props) {
|
|
|
15591
15603
|
}
|
|
15592
15604
|
)
|
|
15593
15605
|
] }),
|
|
15606
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: ServerForm_default.checkRow, children: [
|
|
15607
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
15608
|
+
"input",
|
|
15609
|
+
{
|
|
15610
|
+
type: "checkbox",
|
|
15611
|
+
checked: draft.oauth,
|
|
15612
|
+
onChange: (event) => update({ oauth: event.currentTarget.checked })
|
|
15613
|
+
}
|
|
15614
|
+
),
|
|
15615
|
+
t("oauthAuth")
|
|
15616
|
+
] }),
|
|
15617
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: ServerForm_default.hint, children: t("oauthAuthHint") }),
|
|
15594
15618
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: ServerForm_default.checkRow, children: [
|
|
15595
15619
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
15596
15620
|
"input",
|
|
@@ -15663,6 +15687,7 @@ function serversToJsonText(servers) {
|
|
|
15663
15687
|
}
|
|
15664
15688
|
entry.toolCallTimeoutMs = server.toolCallTimeoutMs;
|
|
15665
15689
|
entry.failOnStartupError = server.failOnStartupError;
|
|
15690
|
+
if (server.oauth === true) entry.oauth = true;
|
|
15666
15691
|
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 } : {} });
|
|
15667
15692
|
document2[server.serverName] = entry;
|
|
15668
15693
|
}
|
|
@@ -15730,6 +15755,7 @@ function parseServersJson(text) {
|
|
|
15730
15755
|
url: typeof cfg.url === "string" ? cfg.url : "",
|
|
15731
15756
|
headers: cfg.headers,
|
|
15732
15757
|
toolCallTimeoutMs: typeof cfg.toolCallTimeoutMs === "number" ? cfg.toolCallTimeoutMs : 6e4,
|
|
15758
|
+
oauth: cfg.oauth === true,
|
|
15733
15759
|
failOnStartupError: cfg.failOnStartupError !== false
|
|
15734
15760
|
};
|
|
15735
15761
|
servers.push({ server, env });
|
|
@@ -16093,6 +16119,7 @@ function viewToDraft(server) {
|
|
|
16093
16119
|
url: server.url,
|
|
16094
16120
|
headersText: server.headers.map((header) => `${header.name}: ${header.value}`).join("\n"),
|
|
16095
16121
|
toolCallTimeoutMs: String(server.toolCallTimeoutMs),
|
|
16122
|
+
oauth: server.oauth === true,
|
|
16096
16123
|
failOnStartupError: server.failOnStartupError
|
|
16097
16124
|
};
|
|
16098
16125
|
}
|
|
@@ -16515,6 +16542,8 @@ var zh = {
|
|
|
16515
16542
|
removeEnvVar: "\u79FB\u9664\u8BE5\u53D8\u91CF",
|
|
16516
16543
|
toolCallTimeoutMs: "\u5DE5\u5177\u8C03\u7528\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",
|
|
16517
16544
|
failOnStartupError: "\u542F\u52A8\u8FDE\u63A5\u5931\u8D25\u65F6\u62D2\u7EDD\u6302\u8F7D",
|
|
16545
|
+
oauthAuth: "\u4F7F\u7528 OAuth \u6388\u6743\uFF08\u6D4F\u89C8\u5668\u6388\u6743 + token \u6258\u7BA1\uFF09",
|
|
16546
|
+
oauthAuthHint: "\u4EC5\u5F53\u8BE5\u670D\u52A1\u5668\u786E\u5B9E\u4F7F\u7528 OAuth\uFF08MCP \u6388\u6743\u7801 + PKCE\uFF09\u65F6\u52FE\u9009\u3002\u7528\u9759\u6001 token \u6216\u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\u8BA4\u8BC1\u7684\u670D\u52A1\u5668\u8BF7\u52FF\u52FE\u9009\u2014\u2014\u5426\u5219\u670D\u52A1\u5668\u8FD4\u56DE 401 \u65F6\u4F1A\u88AB\u5F53\u6210 OAuth \u6311\u6218\uFF0C\u4ECE\u800C\u53CD\u590D\u5F39\u51FA\u6D4F\u89C8\u5668\u6388\u6743\u3002",
|
|
16518
16547
|
statusMounting: "\u8FDE\u63A5\u4E2D",
|
|
16519
16548
|
statusLive: "\u5DF2\u8FDE\u63A5",
|
|
16520
16549
|
statusFailed: "\u6302\u8F7D\u5931\u8D25",
|
|
@@ -16638,6 +16667,8 @@ var en = {
|
|
|
16638
16667
|
removeEnvVar: "Remove this variable",
|
|
16639
16668
|
toolCallTimeoutMs: "Tool-call timeout (ms)",
|
|
16640
16669
|
failOnStartupError: "Reject the mount when the startup connection fails",
|
|
16670
|
+
oauthAuth: "Use OAuth authorization (browser authorization + token storage)",
|
|
16671
|
+
oauthAuthHint: "Tick only when the server really uses OAuth (MCP authorization-code + PKCE). Servers that authenticate with static tokens or custom headers must leave this off \u2014 otherwise a 401 is treated as an OAuth challenge and the browser authorization opens again and again.",
|
|
16641
16672
|
statusMounting: "Connecting",
|
|
16642
16673
|
statusLive: "Connected",
|
|
16643
16674
|
statusFailed: "Mount failed",
|
package/lib/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { randomBytes } from "node:crypto";
|
|
|
2
2
|
import { Service } from "@deepseek-ai/cordis";
|
|
3
3
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
4
4
|
import * as mcpClient from "./mcp-client.js";
|
|
5
|
-
import { createOAuthProvider } from "./oauth.js";
|
|
5
|
+
import { createOAuthProvider, oauthCredentialRefs } from "./oauth.js";
|
|
6
6
|
import { probeConnection } from "./probe.js";
|
|
7
7
|
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
8
8
|
import { hostText } from "./host-locales.js";
|
|
@@ -56,6 +56,7 @@ const mcpServerRowSchema = z$1.object({
|
|
|
56
56
|
env: z$1.array(envEntrySchema).default([]),
|
|
57
57
|
toolCallTimeoutMs: positiveIntegerSchema,
|
|
58
58
|
failOnStartupError: z$1.boolean().default(false),
|
|
59
|
+
oauth: z$1.boolean().default(false),
|
|
59
60
|
origin: z$1.union([z$1.literal("plugin"), z$1.literal("cordis")]).default("plugin"),
|
|
60
61
|
declaredIn: z$1.string().optional(),
|
|
61
62
|
declaredRowId: z$1.string().optional(),
|
|
@@ -184,6 +185,9 @@ function toServerRow(id, server, env, previous) {
|
|
|
184
185
|
}),
|
|
185
186
|
toolCallTimeoutMs: server.toolCallTimeoutMs,
|
|
186
187
|
failOnStartupError: server.failOnStartupError,
|
|
188
|
+
// The OAuth switch is explicit: a caller that states nothing (the JSON
|
|
189
|
+
// editor omitting the field) keeps the previous choice.
|
|
190
|
+
oauth: server.oauth === true ? true : server.oauth === false ? false : previous?.oauth === true,
|
|
187
191
|
// Rows created here are the manager's own; mirrors imported from a
|
|
188
192
|
// Cordis patch layer are written by syncDeclared with origin "cordis".
|
|
189
193
|
origin: "plugin",
|
|
@@ -327,7 +331,7 @@ function placeholderName(value, envNames) {
|
|
|
327
331
|
}
|
|
328
332
|
/** Whether two server records carry the same content (mirror refresh guard). */
|
|
329
333
|
function sameRecord(left, right) {
|
|
330
|
-
return left.serverName === right.serverName && left.transport === right.transport && left.enabled === right.enabled && left.command === right.command && left.cwd === right.cwd && left.url === right.url && left.toolCallTimeoutMs === right.toolCallTimeoutMs && left.failOnStartupError === right.failOnStartupError && left.declaredIn === right.declaredIn && left.declaredRowId === right.declaredRowId && left.stale === right.stale && JSON.stringify(left.args) === JSON.stringify(right.args) && JSON.stringify(left.headers) === JSON.stringify(right.headers) && JSON.stringify(left.env) === JSON.stringify(right.env);
|
|
334
|
+
return left.serverName === right.serverName && left.transport === right.transport && left.enabled === right.enabled && left.command === right.command && left.cwd === right.cwd && left.url === right.url && left.toolCallTimeoutMs === right.toolCallTimeoutMs && left.failOnStartupError === right.failOnStartupError && left.oauth === right.oauth && left.declaredIn === right.declaredIn && left.declaredRowId === right.declaredRowId && left.stale === right.stale && JSON.stringify(left.args) === JSON.stringify(right.args) && JSON.stringify(left.headers) === JSON.stringify(right.headers) && JSON.stringify(left.env) === JSON.stringify(right.env);
|
|
331
335
|
}
|
|
332
336
|
/**
|
|
333
337
|
* The managed server id a credential reference belongs to, when the reference
|
|
@@ -561,6 +565,11 @@ let McpManagerService = (() => {
|
|
|
561
565
|
* disabled and re-enabled.
|
|
562
566
|
*/
|
|
563
567
|
everMounted = /* @__PURE__ */ new Set();
|
|
568
|
+
/**
|
|
569
|
+
* Server names whose one automatic browser authorization this process
|
|
570
|
+
* already consumed (see {@link claimAutoBrowser}).
|
|
571
|
+
*/
|
|
572
|
+
autoBrowserOpened = /* @__PURE__ */ new Set();
|
|
564
573
|
/** Per-id operation chain serializing reconcile, remove, and restart jobs. */
|
|
565
574
|
operationTails = /* @__PURE__ */ new Map();
|
|
566
575
|
/** Server ids whose secret writes are this manager's own; skip restart echoes. */
|
|
@@ -594,6 +603,7 @@ let McpManagerService = (() => {
|
|
|
594
603
|
}, "mcp-manager.domainClose");
|
|
595
604
|
this.table = domain.table("servers");
|
|
596
605
|
this.envTable = domain.table("global_env");
|
|
606
|
+
await this.migrateOAuthFlags();
|
|
597
607
|
await this.refreshDeclared(false);
|
|
598
608
|
this.reconcileDeclared();
|
|
599
609
|
this.installToolControl();
|
|
@@ -808,7 +818,11 @@ let McpManagerService = (() => {
|
|
|
808
818
|
...toMirrorRecord(entry),
|
|
809
819
|
origin: "plugin",
|
|
810
820
|
enabled: true,
|
|
811
|
-
stale: false
|
|
821
|
+
stale: false,
|
|
822
|
+
// A declaration with no `Authorization` header is the shape an
|
|
823
|
+
// OAuth-protected server has; default the switch on for the
|
|
824
|
+
// takeover (it stays visible and editable in the form).
|
|
825
|
+
oauth: entry.transport === "streamable-http" && !entry.headers.some((header) => header.name.toLowerCase() === "authorization")
|
|
812
826
|
});
|
|
813
827
|
if (!parsed.success) return this.adoptFailure("MCP_ADOPT_FAILED", parsed.error.issues[0]?.message ?? "invalid declaration");
|
|
814
828
|
const disable = writeManagedIds(entry.declaredIn, [...managedIds(entry.declaredIn), entry.rowId]);
|
|
@@ -1036,19 +1050,64 @@ let McpManagerService = (() => {
|
|
|
1036
1050
|
};
|
|
1037
1051
|
}
|
|
1038
1052
|
/**
|
|
1039
|
-
* Whether one
|
|
1040
|
-
*
|
|
1041
|
-
*
|
|
1042
|
-
*
|
|
1043
|
-
* authorization-code
|
|
1044
|
-
*
|
|
1045
|
-
* @param
|
|
1053
|
+
* Whether one stored server asks for an OAuth provider. The switch is
|
|
1054
|
+
* explicit (`oauth: true`, set in the settings form): a server that
|
|
1055
|
+
* authenticates with static headers must never be guessed into OAuth,
|
|
1056
|
+
* because a 401 from it is not a challenge and starting the
|
|
1057
|
+
* authorization-code flow for it only opens the browser forever.
|
|
1058
|
+
* stdio never needs the provider.
|
|
1059
|
+
* @param row - the stored definition (or the submitted spec).
|
|
1046
1060
|
* @returns true when OAuth (authorization-code + PKCE) should be enabled.
|
|
1047
1061
|
*/
|
|
1048
|
-
wantsOAuthProvider(
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1062
|
+
wantsOAuthProvider(row) {
|
|
1063
|
+
return row?.transport === "streamable-http" && row.oauth === true;
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Claim the one automatic browser authorization this process allows per
|
|
1067
|
+
* server. Without it a server whose 401 can never be satisfied by the
|
|
1068
|
+
* authorization-code flow (or whose token keeps being rejected) reopens
|
|
1069
|
+
* the browser on every mount and reconnect; later attempts surface the
|
|
1070
|
+
* authorization link instead, and `test` still opens the browser because
|
|
1071
|
+
* the user asked for it.
|
|
1072
|
+
* @param serverName - the server the provider belongs to.
|
|
1073
|
+
* @returns true when this process may open the browser automatically.
|
|
1074
|
+
*/
|
|
1075
|
+
claimAutoBrowser(serverName) {
|
|
1076
|
+
if (this.autoBrowserOpened.has(serverName)) return false;
|
|
1077
|
+
this.autoBrowserOpened.add(serverName);
|
|
1078
|
+
return true;
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Give stored rows that completed an OAuth flow before the explicit
|
|
1082
|
+
* switch existed their provider back (`oauth: true`). The switch replaced
|
|
1083
|
+
* a header heuristic, so a row with stored OAuth credentials really does
|
|
1084
|
+
* need authorization; rows without them stay off and stop opening the
|
|
1085
|
+
* browser for a flow that can never succeed.
|
|
1086
|
+
*/
|
|
1087
|
+
async migrateOAuthFlags() {
|
|
1088
|
+
const table = this.requireTable();
|
|
1089
|
+
for (const [id, row] of table.entries()) {
|
|
1090
|
+
if (row.oauth === true || row.origin === "cordis") continue;
|
|
1091
|
+
let configured = false;
|
|
1092
|
+
for (const ref of oauthCredentialRefs(row.serverName)) {
|
|
1093
|
+
try {
|
|
1094
|
+
if ((await this.ctx.credentials.describe(ref)).configured) {
|
|
1095
|
+
configured = true;
|
|
1096
|
+
break;
|
|
1097
|
+
}
|
|
1098
|
+
} catch {
|
|
1099
|
+
/* an absent credential document is not an error */
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
if (!configured) continue;
|
|
1103
|
+
const parsed = mcpServerRowSchema.safeParse({
|
|
1104
|
+
...row,
|
|
1105
|
+
oauth: true
|
|
1106
|
+
});
|
|
1107
|
+
if (!parsed.success) continue;
|
|
1108
|
+
await table.put(id, parsed.data);
|
|
1109
|
+
this.ctx.logger.info(`mcp-manager: "${row.serverName}" keeps OAuth (stored credentials) after the explicit switch`);
|
|
1110
|
+
}
|
|
1052
1111
|
}
|
|
1053
1112
|
/**
|
|
1054
1113
|
* Mount one server's mcp-client instance without awaiting activation, so a
|
|
@@ -1071,15 +1130,17 @@ let McpManagerService = (() => {
|
|
|
1071
1130
|
const env = await this.resolveEnv(row);
|
|
1072
1131
|
const globalEnv = await this.resolveGlobalEnv();
|
|
1073
1132
|
const config = this.toClientConfig(row, env, globalEnv);
|
|
1074
|
-
// OAuth
|
|
1075
|
-
//
|
|
1076
|
-
//
|
|
1077
|
-
//
|
|
1078
|
-
//
|
|
1079
|
-
//
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1133
|
+
// The OAuth switch is explicit per server (`oauth: true`), and
|
|
1134
|
+
// automatic browser authorization happens at most once per
|
|
1135
|
+
// server per process: later attempts surface the
|
|
1136
|
+
// authorization link instead of opening the browser again.
|
|
1137
|
+
// Tokens are keyed by serverName, so a test-connection
|
|
1138
|
+
// authorization is reused by the mount.
|
|
1139
|
+
const authProvider = this.wantsOAuthProvider(row)
|
|
1140
|
+
? createOAuthProvider(row.serverName, this.ctx, {
|
|
1141
|
+
allowBrowser: this.allowBrowserOnMount && this.claimAutoBrowser(row.serverName),
|
|
1142
|
+
serverUrl: row.url
|
|
1143
|
+
})
|
|
1083
1144
|
: void 0;
|
|
1084
1145
|
handle = this.ctx.plugin(MCP_CLIENT_PLUGIN, { ...config, ...authProvider === void 0 ? {} : { authProvider } });
|
|
1085
1146
|
} catch (error) {
|
|
@@ -1182,6 +1243,7 @@ let McpManagerService = (() => {
|
|
|
1182
1243
|
env,
|
|
1183
1244
|
toolCallTimeoutMs: row.toolCallTimeoutMs,
|
|
1184
1245
|
failOnStartupError: row.failOnStartupError,
|
|
1246
|
+
...(mirror ? {} : { oauth: row.oauth === true }),
|
|
1185
1247
|
...(mirrorNeedsPlugin ? { needsPlugin: true } : {}),
|
|
1186
1248
|
status: {
|
|
1187
1249
|
// A mirror never mounts here: its phase follows the declaration
|
|
@@ -1393,7 +1455,8 @@ let McpManagerService = (() => {
|
|
|
1393
1455
|
url: typeof item.server.url === "string" ? item.server.url : "",
|
|
1394
1456
|
headers: normalizeHeaders(item.server.headers),
|
|
1395
1457
|
toolCallTimeoutMs: Number.isInteger(item.server.toolCallTimeoutMs) ? item.server.toolCallTimeoutMs : 60e3,
|
|
1396
|
-
failOnStartupError: item.server.failOnStartupError !== false
|
|
1458
|
+
failOnStartupError: item.server.failOnStartupError !== false,
|
|
1459
|
+
oauth: item.server.oauth === void 0 ? void 0 : item.server.oauth === true
|
|
1397
1460
|
};
|
|
1398
1461
|
try {
|
|
1399
1462
|
validateServerInput(spec, env);
|
|
@@ -1595,12 +1658,13 @@ let McpManagerService = (() => {
|
|
|
1595
1658
|
// Test connection also supports OAuth: an OAuth-protected server
|
|
1596
1659
|
// opens the browser for authorization and the probe continues after
|
|
1597
1660
|
// tokens are stored (keyed by serverName, matching the mount path).
|
|
1598
|
-
//
|
|
1599
|
-
// the
|
|
1600
|
-
//
|
|
1661
|
+
// The user asked for this probe, so it may open the browser even
|
|
1662
|
+
// when the mount-time circuit breaker already fired; servers without
|
|
1663
|
+
// the explicit switch skip the provider so a 401 reports an
|
|
1664
|
+
// authentication failure instead of starting a bogus flow.
|
|
1601
1665
|
const probe = await probeConnection(probeConfig, {
|
|
1602
1666
|
timeoutMs: this.probeTimeoutMs,
|
|
1603
|
-
...(this.wantsOAuthProvider(
|
|
1667
|
+
...(this.wantsOAuthProvider(request.server)
|
|
1604
1668
|
? { authProvider: createOAuthProvider(request.server.serverName, this.ctx, { serverUrl: request.server.url }) }
|
|
1605
1669
|
: {})
|
|
1606
1670
|
});
|
package/lib/oauth.js
CHANGED
|
@@ -77,6 +77,19 @@ function oauthClientRef(serverId) {
|
|
|
77
77
|
return oauthRef(OAUTH_CLIENT_REF_PREFIX, serverId);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* The credential references one server's OAuth flow writes (token + client).
|
|
82
|
+
* Recognising servers that demonstrably completed a flow is how stored rows
|
|
83
|
+
* migrate to the explicit OAuth switch: the switch replaced a header
|
|
84
|
+
* heuristic, and a server with stored OAuth credentials really does need
|
|
85
|
+
* authorization.
|
|
86
|
+
* @param serverId - the server name the flow is keyed by.
|
|
87
|
+
* @returns both credential reference names.
|
|
88
|
+
*/
|
|
89
|
+
export function oauthCredentialRefs(serverId) {
|
|
90
|
+
return [oauthTokenRef(serverId), oauthClientRef(serverId)];
|
|
91
|
+
}
|
|
92
|
+
|
|
80
93
|
/** Open the system browser to one URL, best effort. */
|
|
81
94
|
function openBrowser(url) {
|
|
82
95
|
const command =
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mcp",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.1",
|
|
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",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"url": "https://github.com/ArvinQi/dsh-mcp/issues"
|
|
36
36
|
},
|
|
37
37
|
"dsh": {
|
|
38
|
+
"supported": "0.1.5-rc.1",
|
|
38
39
|
"client": {
|
|
39
40
|
"inject": [
|
|
40
41
|
"@deepseek-ai/dsh-api-remotes",
|
|
@@ -73,6 +73,7 @@ function viewToDraft(server: McpServerView): McpDraft {
|
|
|
73
73
|
url: server.url,
|
|
74
74
|
headersText: server.headers.map(header => `${header.name}: ${header.value}`).join('\n'),
|
|
75
75
|
toolCallTimeoutMs: String(server.toolCallTimeoutMs),
|
|
76
|
+
oauth: server.oauth === true,
|
|
76
77
|
failOnStartupError: server.failOnStartupError,
|
|
77
78
|
}
|
|
78
79
|
}
|
|
@@ -225,6 +225,16 @@ export function McpServerForm(props: McpServerFormProps): ReactNode {
|
|
|
225
225
|
/>
|
|
226
226
|
</label>
|
|
227
227
|
|
|
228
|
+
<label className={css.checkRow}>
|
|
229
|
+
<input
|
|
230
|
+
type="checkbox"
|
|
231
|
+
checked={draft.oauth}
|
|
232
|
+
onChange={(event) => update({ oauth: event.currentTarget.checked })}
|
|
233
|
+
/>
|
|
234
|
+
{t('oauthAuth')}
|
|
235
|
+
</label>
|
|
236
|
+
<p className={css.hint}>{t('oauthAuthHint')}</p>
|
|
237
|
+
|
|
228
238
|
<label className={css.checkRow}>
|
|
229
239
|
<input
|
|
230
240
|
type="checkbox"
|
|
@@ -77,6 +77,7 @@ export function serversToJsonText(servers: readonly McpServerView[]): string {
|
|
|
77
77
|
}
|
|
78
78
|
entry.toolCallTimeoutMs = server.toolCallTimeoutMs
|
|
79
79
|
entry.failOnStartupError = server.failOnStartupError
|
|
80
|
+
if (server.oauth === true) entry.oauth = true
|
|
80
81
|
entry.env = server.env.map(row => row.secret
|
|
81
82
|
? { name: row.name, secret: true, configured: row.configured }
|
|
82
83
|
: { name: row.name, secret: false, ...(row.value !== undefined && row.value.length > 0 ? { value: row.value } : {}) })
|
|
@@ -150,6 +151,7 @@ export function parseServersJson(text: string): { ok: true; servers: readonly Se
|
|
|
150
151
|
url: typeof cfg.url === 'string' ? cfg.url : '',
|
|
151
152
|
headers: cfg.headers,
|
|
152
153
|
toolCallTimeoutMs: typeof cfg.toolCallTimeoutMs === 'number' ? cfg.toolCallTimeoutMs : 60000,
|
|
154
|
+
oauth: cfg.oauth === true,
|
|
153
155
|
failOnStartupError: cfg.failOnStartupError !== false,
|
|
154
156
|
}
|
|
155
157
|
servers.push({ server, env })
|
package/src/client/locales.ts
CHANGED
|
@@ -39,6 +39,8 @@ export const zh = {
|
|
|
39
39
|
removeEnvVar: '移除该变量',
|
|
40
40
|
toolCallTimeoutMs: '工具调用超时(毫秒)',
|
|
41
41
|
failOnStartupError: '启动连接失败时拒绝挂载',
|
|
42
|
+
oauthAuth: '使用 OAuth 授权(浏览器授权 + token 托管)',
|
|
43
|
+
oauthAuthHint: '仅当该服务器确实使用 OAuth(MCP 授权码 + PKCE)时勾选。用静态 token 或自定义请求头认证的服务器请勿勾选——否则服务器返回 401 时会被当成 OAuth 挑战,从而反复弹出浏览器授权。',
|
|
42
44
|
statusMounting: '连接中',
|
|
43
45
|
statusLive: '已连接',
|
|
44
46
|
statusFailed: '挂载失败',
|
|
@@ -167,6 +169,8 @@ export const en = {
|
|
|
167
169
|
removeEnvVar: 'Remove this variable',
|
|
168
170
|
toolCallTimeoutMs: 'Tool-call timeout (ms)',
|
|
169
171
|
failOnStartupError: 'Reject the mount when the startup connection fails',
|
|
172
|
+
oauthAuth: 'Use OAuth authorization (browser authorization + token storage)',
|
|
173
|
+
oauthAuthHint: 'Tick only when the server really uses OAuth (MCP authorization-code + PKCE). Servers that authenticate with static tokens or custom headers must leave this off — otherwise a 401 is treated as an OAuth challenge and the browser authorization opens again and again.',
|
|
170
174
|
statusMounting: 'Connecting',
|
|
171
175
|
statusLive: 'Connected',
|
|
172
176
|
statusFailed: 'Mount failed',
|
package/src/client/mcp-store.ts
CHANGED
|
@@ -25,6 +25,8 @@ export interface McpDraft {
|
|
|
25
25
|
readonly headersText: string
|
|
26
26
|
/** Timeout as editor text; blank falls back to the default. */
|
|
27
27
|
readonly toolCallTimeoutMs: string
|
|
28
|
+
/** Explicit OAuth switch: only then does the Host attach an OAuth provider. */
|
|
29
|
+
readonly oauth: boolean
|
|
28
30
|
readonly failOnStartupError: boolean
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -78,6 +80,9 @@ export function emptyDraft(): McpDraft {
|
|
|
78
80
|
// Reject the mount by default when the startup connection fails, so a
|
|
79
81
|
// broken server never registers stale tools.
|
|
80
82
|
failOnStartupError: true,
|
|
83
|
+
// OAuth is opt-in: guessing it from the headers opened the browser for
|
|
84
|
+
// servers that authenticate with static tokens.
|
|
85
|
+
oauth: false,
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
88
|
|
|
@@ -94,6 +99,7 @@ export function draftFromServer(server: McpServerView): McpDraft {
|
|
|
94
99
|
url: server.url,
|
|
95
100
|
headersText: server.headers.map(header => `${header.name}: ${header.value}`).join('\n'),
|
|
96
101
|
toolCallTimeoutMs: String(server.toolCallTimeoutMs),
|
|
102
|
+
oauth: server.oauth === true,
|
|
97
103
|
failOnStartupError: server.failOnStartupError,
|
|
98
104
|
}
|
|
99
105
|
}
|
|
@@ -205,6 +211,7 @@ export function draftToSubmission(draft: McpDraft): McpSubmission {
|
|
|
205
211
|
url: draft.url.trim(),
|
|
206
212
|
headers: parseHeaders(draft.headersText),
|
|
207
213
|
toolCallTimeoutMs: parseTimeout(draft.toolCallTimeoutMs),
|
|
214
|
+
oauth: draft.oauth,
|
|
208
215
|
failOnStartupError: draft.failOnStartupError,
|
|
209
216
|
},
|
|
210
217
|
}
|
|
@@ -36,6 +36,7 @@ const _deepseek_ai_dsh_mcp_manager_mcpManager_list_result$schema = z.object({
|
|
|
36
36
|
})).readonly(),
|
|
37
37
|
'toolCallTimeoutMs': z.number().readonly(),
|
|
38
38
|
'failOnStartupError': z.boolean().readonly(),
|
|
39
|
+
'oauth': z.boolean().readonly().optional(),
|
|
39
40
|
'status': z.object({
|
|
40
41
|
'phase': z.union([z.literal("failed"), z.literal("stopped"), z.literal("mounting"), z.literal("live")]).readonly(),
|
|
41
42
|
'tools': z.array(z.string()).readonly(),
|
|
@@ -82,6 +83,7 @@ const _deepseek_ai_dsh_mcp_manager_mcpManager_test_parameter_0$schema = z.object
|
|
|
82
83
|
})).readonly(),
|
|
83
84
|
'toolCallTimeoutMs': z.number().readonly(),
|
|
84
85
|
'failOnStartupError': z.boolean().readonly(),
|
|
86
|
+
'oauth': z.boolean().readonly().optional(),
|
|
85
87
|
}).readonly(),
|
|
86
88
|
'env': z.array(z.object({
|
|
87
89
|
'name': z.string().readonly(),
|
|
@@ -119,6 +121,7 @@ const _deepseek_ai_dsh_mcp_manager_mcpManager_upsert_parameter_0$schema = z.obje
|
|
|
119
121
|
})).readonly(),
|
|
120
122
|
'toolCallTimeoutMs': z.number().readonly(),
|
|
121
123
|
'failOnStartupError': z.boolean().readonly(),
|
|
124
|
+
'oauth': z.boolean().readonly().optional(),
|
|
122
125
|
}).readonly(),
|
|
123
126
|
'env': z.array(z.object({
|
|
124
127
|
'name': z.string().readonly(),
|
|
@@ -149,6 +152,7 @@ const _deepseek_ai_dsh_mcp_manager_mcpManager_upsert_result$schema = z.union([z.
|
|
|
149
152
|
})).readonly(),
|
|
150
153
|
'toolCallTimeoutMs': z.number().readonly(),
|
|
151
154
|
'failOnStartupError': z.boolean().readonly(),
|
|
155
|
+
'oauth': z.boolean().readonly().optional(),
|
|
152
156
|
'status': z.object({
|
|
153
157
|
'phase': z.union([z.literal("failed"), z.literal("stopped"), z.literal("mounting"), z.literal("live")]).readonly(),
|
|
154
158
|
'tools': z.array(z.string()).readonly(),
|
|
@@ -191,6 +195,7 @@ const _deepseek_ai_dsh_mcp_manager_mcpManager_upsertJson_parameter_0$schema = z.
|
|
|
191
195
|
})).readonly()]).readonly().optional(),
|
|
192
196
|
'toolCallTimeoutMs': z.number().readonly().optional(),
|
|
193
197
|
'failOnStartupError': z.boolean().readonly().optional(),
|
|
198
|
+
'oauth': z.boolean().readonly().optional(),
|
|
194
199
|
}).readonly(),
|
|
195
200
|
'env': z.array(z.object({
|
|
196
201
|
'name': z.string().readonly(),
|
|
@@ -226,6 +231,7 @@ const _deepseek_ai_dsh_mcp_manager_mcpManager_upsertJson_result$schema = z.union
|
|
|
226
231
|
})).readonly(),
|
|
227
232
|
'toolCallTimeoutMs': z.number().readonly(),
|
|
228
233
|
'failOnStartupError': z.boolean().readonly(),
|
|
234
|
+
'oauth': z.boolean().readonly().optional(),
|
|
229
235
|
'status': z.object({
|
|
230
236
|
'phase': z.union([z.literal("failed"), z.literal("stopped"), z.literal("mounting"), z.literal("live")]).readonly(),
|
|
231
237
|
'tools': z.array(z.string()).readonly(),
|
|
@@ -347,6 +353,7 @@ const _deepseek_ai_dsh_mcp_manager_mcpManager_adopt_result$schema = z.union([z.o
|
|
|
347
353
|
})).readonly(),
|
|
348
354
|
'toolCallTimeoutMs': z.number().readonly(),
|
|
349
355
|
'failOnStartupError': z.boolean().readonly(),
|
|
356
|
+
'oauth': z.boolean().readonly().optional(),
|
|
350
357
|
'status': z.object({
|
|
351
358
|
'phase': z.union([z.literal("failed"), z.literal("stopped"), z.literal("mounting"), z.literal("live")]).readonly(),
|
|
352
359
|
'tools': z.array(z.string()).readonly(),
|
package/src/client/types.ts
CHANGED
|
@@ -44,6 +44,8 @@ export interface McpServerInput {
|
|
|
44
44
|
readonly url: string
|
|
45
45
|
readonly headers: readonly McpHeaderEntry[]
|
|
46
46
|
readonly toolCallTimeoutMs: number
|
|
47
|
+
/** Explicit OAuth switch: only then does the Host attach an OAuth provider. */
|
|
48
|
+
readonly oauth: boolean
|
|
47
49
|
readonly failOnStartupError: boolean
|
|
48
50
|
}
|
|
49
51
|
|
|
@@ -86,6 +88,8 @@ export interface McpServerView {
|
|
|
86
88
|
readonly env: readonly McpEnvVarView[]
|
|
87
89
|
readonly toolCallTimeoutMs: number
|
|
88
90
|
readonly failOnStartupError: boolean
|
|
91
|
+
/** Explicit OAuth switch of a managed row (absent for declared rows). */
|
|
92
|
+
readonly oauth?: boolean
|
|
89
93
|
readonly status: McpServerStatus
|
|
90
94
|
/** `plugin` = stored and mounted here; `cordis` = declared in a patch layer. */
|
|
91
95
|
readonly source?: McpServerSource
|