dsh-mcp-connector 0.2.26 → 0.2.28

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.md CHANGED
@@ -4,6 +4,29 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.2.28] - 2026-08-29
8
+
9
+ ### Documentation
10
+
11
+ - 重写 npm 和中英文 README 首屏产品介绍,前置 MCP Server 连接、OAuth 2.0 PKCE、stdio/HTTP、Registry 与工具/Prompt 发现等高意图能力。
12
+ - 增加 npm 下载、GitHub Star/Fork/Release 徽标、真实演示、能力对比、中文教程入口与独立贡献指南。
13
+ - 扩充 `model-context-protocol`、`mcp-registry`、`mcp-manager`、`streamable-http` 等高意图检索词。
14
+
15
+ ### Verification
16
+
17
+ - 新增中英文 README 与 `package.json` 版本一致性门禁和 4 项回归测试;138 项自动测试、lint 与 npm 发布包白名单/敏感内容扫描通过。
18
+
19
+ ## [0.2.27] - 2026-08-28
20
+
21
+ ### Fixed
22
+
23
+ - OAuth Refresh Token 轮换不再依赖 DSH JSON storage 的进程内缓存;新增每 Grant 独立原子日志和跨进程锁,Desktop 与 `dsh web` 同时运行时只轮换一次 Token,等待方直接采用最新凭据。
24
+ - DSH Desktop 没有插件市场设置分区时,更新入口不再停留在普通设置页;改为中性的“查看更新方式”,并在确认无市场分区后打开 npm 安装说明。
25
+
26
+ ### Security
27
+
28
+ - 跨进程 Grant journal 位于 `$DSH_HOME/storages/mcp_connector_grants_v1`,采用 0700 目录、0600 文件和同目录原子替换;Token 不进入运行日志、页面或 Git 仓库。
29
+
7
30
  ## [0.2.26] - 2026-08-27
8
31
 
9
32
  ### Fixed
@@ -426,7 +449,9 @@
426
449
  - 外部 URL 与导入 Header 执行安全校验。
427
450
  - iframe 消息校验同源和消息来源。
428
451
 
429
- [Unreleased]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.26...HEAD
452
+ [Unreleased]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.28...HEAD
453
+ [0.2.28]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.27...v0.2.28
454
+ [0.2.27]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.26...v0.2.27
430
455
  [0.2.26]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.25...v0.2.26
431
456
  [0.2.25]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.24...v0.2.25
432
457
  [0.2.24]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.23...v0.2.24
@@ -0,0 +1,55 @@
1
+ # 贡献指南 / Contributing
2
+
3
+ 感谢你帮助改进 `dsh-mcp-connector`。请不要在 Issue、PR、日志、截图或测试数据中提交 Token、API Key、Cookie、OAuth 凭据或真实业务数据。
4
+
5
+ Thank you for contributing. Never put tokens, API keys, cookies, OAuth credentials, or real business data in issues, pull requests, logs, screenshots, or fixtures.
6
+
7
+ ## 参与方式
8
+
9
+ - 报告缺陷或提交功能建议:[GitHub Issues](https://github.com/duhu2000/dsh-mcp-connector/issues)
10
+ - 为公共市场增加连接器:阅读 [Connector onboarding](https://github.com/duhu2000/dsh-mcp-connector-registry/blob/main/docs/ONBOARDING.md),连接器描述与插件代码分仓管理。
11
+ - 修复插件或改进文档:Fork 本仓库,从最新 `main` 创建单一目的的分支并提交 PR。
12
+ - 搭建私有目录:参考 [README 配置说明](README.md#配置) 和 [市场注册指南](docs/MARKET-REGISTRATION.md)。
13
+
14
+ ## 本地开发
15
+
16
+ 要求 Node.js 20 或更高版本。DSH 运行期 peer dependencies 由 Host 提供,本地安装使用:
17
+
18
+ ```bash
19
+ npm install --legacy-peer-deps
20
+ npm run check
21
+ ```
22
+
23
+ 如果修改公共 Registry 或市场元数据,还应运行:
24
+
25
+ ```bash
26
+ npm run registry:build
27
+ npm run registry:validate
28
+ npm run market:check
29
+ ```
30
+
31
+ UI 可以在无真实凭据的 mock 环境中验收:
32
+
33
+ ```bash
34
+ npm run dev:ui
35
+ ```
36
+
37
+ ## PR 要求
38
+
39
+ 1. 一个 PR 解决一个明确问题,不要夹带无关重构。
40
+ 2. 新增或修改行为时增加对应测试;纯文档改动也必须通过 `npm run check`。
41
+ 3. 中英文用户文档应同步更新。
42
+ 4. README 中的当前版本必须与 `package.json` 一致,`npm run docs:check` 会阻断版本漂移。
43
+ 5. 只使用无凭据 mock 或已脱敏数据做测试与截图。
44
+
45
+ ## 从 Fork 到 PR
46
+
47
+ ```bash
48
+ git switch main
49
+ git pull --ff-only
50
+ git switch -c feat/<short-purpose>
51
+ npm install --legacy-peer-deps
52
+ npm run check
53
+ ```
54
+
55
+ 提交时说明用户问题、解决方案、测试结果与必要的手工验收步骤。
package/README.en.md CHANGED
@@ -1,21 +1,50 @@
1
- # MCP Connector and Connection Manager Marketplace for DeepSeek Harness
1
+ # MCP Connector and MCP Server Marketplace for DeepSeek Harness
2
2
 
3
- > A general-purpose MCP connector, plugin extension, and integration marketplace initiated and maintained by the Qichacha (QCC) team
3
+ > A universal MCP connector and marketplace for connecting, authorizing, discovering, and managing MCP servers, initiated and maintained by the Qichacha (QCC) team
4
4
 
5
- Browse and install MCP connectors from different providers in DeepSeek Harness Desktop. Connect MCP servers through OAuth, API key/URL configuration, JSON import, or a connector descriptor URL; discover tools and prompts, extend agent skills, start a new conversation, and manage installed connections.
5
+ Manage MCP connections from different providers in one place inside DeepSeek Harness Desktop/Web. Use OAuth 2.0 PKCE, API keys, stdio/HTTP, `mcpServers` JSON import, tool and prompt discovery, and an independently updated Registry of curated connectors.
6
6
 
7
7
  > Here, “skill extension” means extending an agent through MCP tools and prompts; this package does not present itself as a standalone DSH Skill.
8
8
 
9
9
  [简体中文](README.md)
10
10
 
11
- [Chinese user guide](docs/USER-GUIDE.md) · [Connector onboarding](https://github.com/duhu2000/dsh-mcp-connector-registry/blob/main/docs/ONBOARDING.md) · [Issues](https://github.com/duhu2000/dsh-mcp-connector/issues)
11
+ [Chinese user guide](docs/USER-GUIDE.md) · [Connector onboarding](https://github.com/duhu2000/dsh-mcp-connector-registry/blob/main/docs/ONBOARDING.md) · [Contributing](CONTRIBUTING.md) · [Issues](https://github.com/duhu2000/dsh-mcp-connector/issues)
12
12
 
13
13
  [![CI](https://github.com/duhu2000/dsh-mcp-connector/actions/workflows/ci.yml/badge.svg)](https://github.com/duhu2000/dsh-mcp-connector/actions/workflows/ci.yml)
14
14
  [![npm](https://img.shields.io/npm/v/dsh-mcp-connector.svg)](https://www.npmjs.com/package/dsh-mcp-connector)
15
+ [![npm downloads](https://img.shields.io/npm/dm/dsh-mcp-connector.svg)](https://www.npmjs.com/package/dsh-mcp-connector)
16
+ [![GitHub stars](https://img.shields.io/github/stars/duhu2000/dsh-mcp-connector?style=flat)](https://github.com/duhu2000/dsh-mcp-connector/stargazers)
17
+ [![GitHub forks](https://img.shields.io/github/forks/duhu2000/dsh-mcp-connector?style=flat)](https://github.com/duhu2000/dsh-mcp-connector/forks)
18
+ [![GitHub Release](https://img.shields.io/github/v/release/duhu2000/dsh-mcp-connector)](https://github.com/duhu2000/dsh-mcp-connector/releases/latest)
15
19
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
16
20
  [![Registry connectors](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fduhu2000%2Fdsh-mcp-connector-registry%2Fmain%2Fcatalog-stats.json&query=%24.registryCount&label=Registry%20connectors&color=5865f2)](https://github.com/duhu2000/dsh-mcp-connector-registry/blob/main/catalog-stats.json)
17
21
  [![Marketplace cards](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fduhu2000%2Fdsh-mcp-connector-registry%2Fmain%2Fcatalog-stats.json&query=%24.marketCount&label=Marketplace%20cards&color=16a34a)](https://github.com/duhu2000/dsh-mcp-connector-registry/blob/main/catalog-stats.json)
18
22
 
23
+ ## Start in 30 seconds
24
+
25
+ ```bash
26
+ dsh plugin --profile web add dsh-mcp-connector
27
+ ```
28
+
29
+ Fully restart DeepSeek Harness Desktop or `dsh web` after installation or upgrade, then open **MCP Connector** in the primary sidebar.
30
+
31
+ ![16-second MCP Connector walkthrough](https://raw.githubusercontent.com/duhu2000/dsh-mcp-connector/main/docs/demo.gif)
32
+
33
+ If the plugin helps you connect an MCP server faster, consider [starring the repository](https://github.com/duhu2000/dsh-mcp-connector/stargazers). Connector submissions, compatibility fixes, and documentation pull requests are welcome.
34
+
35
+ ## Why use MCP Connector
36
+
37
+ | Capability | Basic MCP configuration panel | MCP Connector |
38
+ |---|:---:|:---:|
39
+ | Manual MCP server configuration | ✅ | ✅ |
40
+ | Independently updated curated connector catalog | Usually not included | ✅ |
41
+ | OAuth 2.0 PKCE and API keys | Partial | ✅ |
42
+ | HTTP, stdio, and `mcpServers` JSON import | Partial | ✅ |
43
+ | Tool and prompt discovery | Implementation-specific | ✅ |
44
+ | Authorization recovery and connection lifecycle management | Usually not included | ✅ |
45
+ | Connection health checks and Registry refresh | Usually not included | ✅ |
46
+ | Plugin version discovery and safe updates | Usually not included | ✅ |
47
+
19
48
  ## Features
20
49
 
21
50
  - A primary sidebar entry below New Conversation and above workspaces/conversations, with a public footer-slot fallback for incompatible DSH DOM versions.
@@ -25,20 +54,18 @@ Browse and install MCP connectors from different providers in DeepSeek Harness D
25
54
  - Credential and MCP initialize validation before HTTP API-key connectors are saved as installed, plus declarative multi-field credential-to-env bindings for marketplace stdio connectors.
26
55
  - Dynamic tool discovery grouped by MCP server, including descriptions, search, batched rendering, and an independent scroll region.
27
56
  - Curated prompt templates that can open a DSH conversation and prefill its draft; missing variables are requested before the prompt is sent.
28
- - Persistent connection lifecycle management: restore on restart, enable/disable, disconnect, retry transient OAuth refresh failures with bounded backoff, and revoke authorization. Cards declaring issuer-level sharing reuse one grant; DCR client secrets remain local to that grant.
57
+ - Persistent connection lifecycle management: restore on restart, enable/disable, disconnect, retry transient OAuth refresh failures with bounded backoff, and revoke authorization. Cards declaring issuer-level sharing reuse one grant; a cross-process lock and per-grant atomic journal prevent Desktop and Web hosts from consuming or overwriting the same rotating refresh token.
29
58
  - Built-in, remote, and local catalogs with `published` and `featured` controls.
30
59
  - A standalone remote Registry, allowing new marketplace cards to appear after refresh without publishing a new npm version.
31
- - Plugin version and one-click updates: version discovery is independent of the installation source, while an Update Provider adapter layer negotiates safe mutation capabilities. DSH Market API v1 is the first adapter and supports progress, normalized failures, rollback, and capability-gated restart/refresh actions; without a compatible provider, the UI falls back to the current plugin marketplace or npm.
60
+ - Plugin version and one-click updates: version discovery is independent of the installation source, while an Update Provider adapter layer negotiates safe mutation capabilities. DSH Market API v1 is the first adapter and supports progress, normalized failures, rollback, and capability-gated restart/refresh actions. Without a compatible provider the UI offers update instructions; if Desktop has no plugin-market section, it opens the npm package page instead of leaving the user on generic Settings.
32
61
  - Explicit, non-destructive migration from the two earlier Qichacha OAuth plugins, plus active-plugin conflict detection that blocks duplicate server management and credential overwrites.
33
62
 
34
63
  <!-- catalog-stats:start -->
35
- As of 2026-08-25, the public Registry publishes 78 connector descriptors. After merging and deduplicating them with the 4 bundled Qichacha cards, the Marketplace exposes 82 cards across 9 business categories. Recommendations remain limited to the four Qichacha cards, PKULaw, and Wind, for 6 featured cards in total. The Registry evolves independently; the badge shown after a client refresh and the live badges above are the authoritative current counts.
64
+ As of 2026-08-27, the public Registry publishes 79 connector descriptors. After merging and deduplicating them with the 4 bundled Qichacha cards, the Marketplace exposes 83 cards across 9 business categories. Recommendations remain limited to the four Qichacha cards, PKULaw, and Wind, for 6 featured cards in total. The Registry evolves independently; the badge shown after a client refresh and the live badges above are the authoritative current counts.
36
65
  <!-- catalog-stats:end -->
37
66
 
38
67
  ## Interface and demo
39
68
 
40
- ![16-second MCP Connector walkthrough](https://raw.githubusercontent.com/duhu2000/dsh-mcp-connector/main/docs/demo.gif)
41
-
42
69
  | Marketplace overview | Connector details and curated prompts |
43
70
  |---|---|
44
71
  | ![Marketplace overview](https://raw.githubusercontent.com/duhu2000/dsh-mcp-connector/main/docs/screenshots/01-market-overview.jpg) | ![Connector details](https://raw.githubusercontent.com/duhu2000/dsh-mcp-connector/main/docs/screenshots/02-connector-detail.jpg) |
@@ -74,6 +101,14 @@ Connected tools are exposed to the model with the `mcp__<serverName>__*` prefix.
74
101
 
75
102
  The detailed [Chinese user guide](docs/USER-GUIDE.md) covers category browsing, the four authentication modes, HTTP/stdio configuration, JSON import, connection management, and troubleshooting.
76
103
 
104
+ ## Guides and ecosystem
105
+
106
+ - [User guide: installation, authorization, JSON import, and troubleshooting](docs/USER-GUIDE.md)
107
+ - [Plugin updates: version discovery, providers, and rollback](docs/PLUGIN-UPDATE.md)
108
+ - [Marketplace registration: local cards, the public Registry, and OAuth requirements](docs/MARKET-REGISTRATION.md)
109
+ - [Third-party connector onboarding](https://github.com/duhu2000/dsh-mcp-connector-registry/blob/main/docs/ONBOARDING.md)
110
+ - [Development, forks, and contributions](CONTRIBUTING.md)
111
+
77
112
  ## Connector catalog
78
113
 
79
114
  The package contains a bundled fallback catalog. By default, it refreshes the public [dsh-mcp-connector-registry](https://github.com/duhu2000/dsh-mcp-connector-registry) through jsDelivr, then tries GitHub raw if the primary source fails; cached or bundled data remains available if neither remote source can be reached. jsDelivr branch URLs can lag behind a newly merged registry commit, so new cards may not appear immediately.
@@ -108,17 +143,17 @@ npm run market:check
108
143
  npm run dev:ui
109
144
  ```
110
145
 
111
- `npm run check` performs syntax checks, automated tests, and an npm package allowlist/sensitive-content audit. `npm run market:check` tracks the external DSH marketplace PR and live directory. Tags matching `v*` trigger GitHub Actions; the tag must match `package.json`. npm releases use Trusted Publishing through GitHub OIDC and do not require a long-lived `NPM_TOKEN`.
146
+ `npm run check` performs syntax checks, README/package version consistency validation, automated tests, and an npm package allowlist/sensitive-content audit. `npm run market:check` tracks the external DSH marketplace PR and live directory. Tags matching `v*` trigger GitHub Actions; the tag must match `package.json`. npm releases use Trusted Publishing through GitHub OIDC and do not require a long-lived `NPM_TOKEN`.
112
147
 
113
148
  Every Registry merge regenerates `catalog-stats.json`; an hourly workflow in this repository synchronizes the Chinese and English product copy plus a local stats snapshot. The static npm README updates with package releases, while the live badges above read the Registry directly and therefore stay current without another npm release.
114
149
 
115
- The current public version is [`dsh-mcp-connector@0.2.24`](https://www.npmjs.com/package/dsh-mcp-connector), with [GitHub Release v0.2.24](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.24).
150
+ The current public version is [`dsh-mcp-connector@0.2.28`](https://www.npmjs.com/package/dsh-mcp-connector), with [GitHub Release v0.2.28](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.28).
116
151
 
117
152
  See [CHANGELOG.md](CHANGELOG.md) for version history and [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md) for the Desktop release checklist.
118
153
 
119
154
  ## Security and limitations
120
155
 
121
- - Credentials are stored only in the DSH storage domain and are not written to the catalog, Git repository, or conversation history.
156
+ - Credentials stay inside the local DSH storage boundary: connection records use the storage domain, while rotating OAuth credentials are also saved in `$DSH_HOME/storages/mcp_connector_grants_v1` so stale host snapshots cannot overwrite them. The directory is mode 0700 and records are mode 0600; credentials are never written to the catalog, Git repository, page, logs, or conversation history.
122
157
  - Failed API key/token validation is not persisted; authentication, timeout, DNS, and TLS/network errors are reported separately.
123
158
  - External URLs must use HTTPS; HTTP is allowed only for loopback development.
124
159
  - Remote descriptors and catalogs are limited to 2 MiB, Web API requests to 1 MiB, and imported JSON is scanned for credential fields before normalization.
package/README.md CHANGED
@@ -1,21 +1,50 @@
1
- # MCP连接器与连接管理市场(DeepSeek Harness 插件 / 扩展)
1
+ # MCP连接器:DeepSeek Harness MCP Server 连接与管理市场
2
2
 
3
- > 通用 MCP Connector / Connection Manager / Integration Marketplace,由企查查(Qichacha/QCC)团队发起并维护
3
+ > 连接、授权、发现和管理 MCP Server 的通用 MCP Connector Marketplace,由企查查(Qichacha/QCC)团队发起并维护
4
4
 
5
- 在 DeepSeek Harness Desktop 中浏览和安装不同厂商的 MCP连接器,连接 MCP Server,通过 OAuth、API Key/URL JSON 接入服务,发现工具与 Prompt,扩展 AI 技能,并管理已安装连接。
5
+ 在 DeepSeek Harness Desktop/Web 中一站式管理不同厂商的 MCP 连接:支持 OAuth 2.0 PKCE、API Key、stdio/HTTP、`mcpServers` JSON 导入、工具与 Prompt 发现,并通过独立 Registry 持续更新精选连接器目录。
6
6
 
7
7
  > 注:“技能扩展”指通过 MCP 工具和 Prompt 扩展智能体能力,本包不会伪装成独立 DSH Skill。
8
8
 
9
9
  [English](README.en.md)
10
10
 
11
- [用户手册](docs/USER-GUIDE.md) · [第三方连接器上架指南](https://github.com/duhu2000/dsh-mcp-connector-registry/blob/main/docs/ONBOARDING.md) · [问题反馈](https://github.com/duhu2000/dsh-mcp-connector/issues)
11
+ [用户手册](docs/USER-GUIDE.md) · [第三方连接器上架指南](https://github.com/duhu2000/dsh-mcp-connector-registry/blob/main/docs/ONBOARDING.md) · [参与贡献](CONTRIBUTING.md) · [问题反馈](https://github.com/duhu2000/dsh-mcp-connector/issues)
12
12
 
13
13
  [![CI](https://github.com/duhu2000/dsh-mcp-connector/actions/workflows/ci.yml/badge.svg)](https://github.com/duhu2000/dsh-mcp-connector/actions/workflows/ci.yml)
14
14
  [![npm](https://img.shields.io/npm/v/dsh-mcp-connector.svg)](https://www.npmjs.com/package/dsh-mcp-connector)
15
+ [![npm downloads](https://img.shields.io/npm/dm/dsh-mcp-connector.svg)](https://www.npmjs.com/package/dsh-mcp-connector)
16
+ [![GitHub stars](https://img.shields.io/github/stars/duhu2000/dsh-mcp-connector?style=flat)](https://github.com/duhu2000/dsh-mcp-connector/stargazers)
17
+ [![GitHub forks](https://img.shields.io/github/forks/duhu2000/dsh-mcp-connector?style=flat)](https://github.com/duhu2000/dsh-mcp-connector/forks)
18
+ [![GitHub Release](https://img.shields.io/github/v/release/duhu2000/dsh-mcp-connector)](https://github.com/duhu2000/dsh-mcp-connector/releases/latest)
15
19
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
16
20
  [![Registry connectors](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fduhu2000%2Fdsh-mcp-connector-registry%2Fmain%2Fcatalog-stats.json&query=%24.registryCount&label=Registry%20connectors&color=5865f2)](https://github.com/duhu2000/dsh-mcp-connector-registry/blob/main/catalog-stats.json)
17
21
  [![Marketplace cards](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fduhu2000%2Fdsh-mcp-connector-registry%2Fmain%2Fcatalog-stats.json&query=%24.marketCount&label=Marketplace%20cards&color=16a34a)](https://github.com/duhu2000/dsh-mcp-connector-registry/blob/main/catalog-stats.json)
18
22
 
23
+ ## 30 秒开始
24
+
25
+ ```bash
26
+ dsh plugin --profile web add dsh-mcp-connector
27
+ ```
28
+
29
+ 安装或升级后完全重启 DeepSeek Harness Desktop 或 `dsh web`,然后打开左侧「🧩 MCP连接器」。
30
+
31
+ ![MCP 连接器 16 秒演示](https://raw.githubusercontent.com/duhu2000/dsh-mcp-connector/main/docs/demo.gif)
32
+
33
+ 如果它帮你更快地接入 MCP Server,欢迎在 [GitHub 点个 Star](https://github.com/duhu2000/dsh-mcp-connector/stargazers);新的连接器、兼容性修复和文档改进也欢迎提交 PR。
34
+
35
+ ## 为什么使用 MCP连接器
36
+
37
+ | 能力 | 普通 MCP 配置面板 | MCP连接器 |
38
+ |---|:---:|:---:|
39
+ | 手工配置 MCP Server | ✅ | ✅ |
40
+ | 持续更新的精选连接器目录 | 通常无 | ✅ |
41
+ | OAuth 2.0 PKCE 与 API Key | 部分 | ✅ |
42
+ | HTTP、stdio 与 `mcpServers` JSON 导入 | 部分 | ✅ |
43
+ | 工具与 Prompt 发现 | 视实现而定 | ✅ |
44
+ | 授权恢复与连接生命周期管理 | 通常无 | ✅ |
45
+ | 连接健康检查与 Registry 刷新 | 通常无 | ✅ |
46
+ | 插件版本检测与安全更新 | 通常无 | ✅ |
47
+
19
48
  ## 功能
20
49
 
21
50
  - 左侧主导航入口:目标位置为“新会话”下方、“工作区/会话列表”上方;若 DSH DOM 结构不兼容,自动回退到底部公开插槽。
@@ -25,7 +54,7 @@
25
54
  - Prompt 模板:使用 `{{company}}` 等变量,发送前填写真实查询主体。
26
55
  - 三种接入:OAuth 2.0 PKCE、自定义 HTTP/stdio、导入 `mcpServers` JSON;也支持从连接器描述 URL 安装。OAuth 动态注册兼容公共客户端以及 `client_secret_post` / `client_secret_basic` 机密客户端。
27
56
  - 市场 Bearer/API Key 连接器先执行 MCP initialize 连通性与凭据校验,全部 HTTP Server 通过后才持久化凭据并进入“已安装”;stdio 卡片可声明多个本机凭据字段及其环境变量映射。
28
- - 生命周期管理:连接持久化、重启恢复、启停、断开、OAuth 自动刷新/退避恢复与撤销;同 issuer 卡片可共享一次授权,DCR 返回的客户端密钥与 Token 一同只保存在本机。
57
+ - 生命周期管理:连接持久化、重启恢复、启停、断开、OAuth 自动刷新/退避恢复与撤销;同 issuer 卡片共享一次授权,跨进程锁与独立原子 Grant journal 防止 Desktop/Web 并行时重复消耗 Refresh Token
29
58
  - 目录运营:内置目录、远程 registry、本地覆盖,支持 `published` 上下架与 `featured` 精选。
30
59
  - 独立远程 Registry:新市场卡片合并后客户端刷新即可见,无需重新发布 npm;远程不可用时自动回退内置目录。
31
60
  - 插件版本与一键更新:版本发现独立于安装来源;页面通过 Update Provider 适配层探测安全更新能力。DSH Market API v1 是首个适配器,支持进度、稳定失败码、回滚及按宿主能力提供的重启/刷新操作;无可用 Provider 时回退到当前插件市场或 npm。
@@ -34,13 +63,11 @@
34
63
  - 对话工具:`mcp_connector_catalog`、`connect`、`configure`、`import_json`、`install_from_url`、`status`、`health_check`、`set_enabled`、`disconnect`、`refresh_catalog`、`publish`、`tools_list`。
35
64
 
36
65
  <!-- catalog-stats:start -->
37
- 截至 2026-08-25,公共 Registry 已发布 78 条连接器描述;与随包的 4 张企查查卡片合并去重后,市场页可浏览 82 张卡片,覆盖企业数据、金融投资、法律合规、开发工具、办公协作、调研分析、设计创意、效率工具、其他 9 类。推荐位严格保留 4 张企查查卡片、北大法宝和 Wind,共 6 张;其他连接器按业务分类展示。Registry 可独立持续更新,实际数量以客户端刷新后的市场页签徽标和上方实时统计徽标为准。
66
+ 截至 2026-08-27,公共 Registry 已发布 79 条连接器描述;与随包的 4 张企查查卡片合并去重后,市场页可浏览 83 张卡片,覆盖企业数据、金融投资、法律合规、开发工具、办公协作、调研分析、设计创意、效率工具、其他 9 类。推荐位严格保留 4 张企查查卡片、北大法宝和 Wind,共 6 张;其他连接器按业务分类展示。Registry 可独立持续更新,实际数量以客户端刷新后的市场页签徽标和上方实时统计徽标为准。
38
67
  <!-- catalog-stats:end -->
39
68
 
40
69
  ## 界面与演示
41
70
 
42
- ![MCP 连接器 16 秒演示](https://raw.githubusercontent.com/duhu2000/dsh-mcp-connector/main/docs/demo.gif)
43
-
44
71
  | 市场总览 | 连接器详情与精选 Prompt |
45
72
  |---|---|
46
73
  | ![市场总览](https://raw.githubusercontent.com/duhu2000/dsh-mcp-connector/main/docs/screenshots/01-market-overview.jpg) | ![连接器详情](https://raw.githubusercontent.com/duhu2000/dsh-mcp-connector/main/docs/screenshots/02-connector-detail.jpg) |
@@ -76,6 +103,14 @@ bash <(curl -fsSL https://raw.githubusercontent.com/duhu2000/dsh-mcp-connector/m
76
103
 
77
104
  分类浏览、四种鉴权状态、自定义 HTTP/stdio、JSON 导入、连接管理与故障排查见完整的[用户手册](docs/USER-GUIDE.md)。
78
105
 
106
+ ## 中文教程与生态入口
107
+
108
+ - [用户手册:安装、授权、JSON 导入与故障排查](docs/USER-GUIDE.md)
109
+ - [插件更新:版本检测、Provider 与回滚](docs/PLUGIN-UPDATE.md)
110
+ - [市场注册:本地卡片、公共 Registry 与 OAuth 要求](docs/MARKET-REGISTRATION.md)
111
+ - [第三方连接器上架指南](https://github.com/duhu2000/dsh-mcp-connector-registry/blob/main/docs/ONBOARDING.md)
112
+ - [开发、Fork 与贡献指南](CONTRIBUTING.md)
113
+
79
114
  ## 配置
80
115
 
81
116
  Bundle 默认配置位于 `cordis.patch.yml`:
@@ -103,11 +138,11 @@ npm run market:check
103
138
  npm run dev:ui
104
139
  ```
105
140
 
106
- `check` 执行语法检查、自动测试和 npm 发布包白名单校验;`market:check` 检查外部 DSH 市场 PR 与线上目录;`dev:ui` 启动不含真实凭据的本地 mock 市场。CI 使用 `--legacy-peer-deps` 安装显式测试依赖,DSH 运行期 peer 仍由 Host 提供。`v*` Tag 会触发 GitHub Actions;Tag 必须与 `package.json` 版本一致。Release 通过 npm Trusted Publishing (GitHub OIDC) 发布,不依赖长期 `NPM_TOKEN`。
141
+ `check` 执行语法检查、README/包版本一致性校验、自动测试和 npm 发布包白名单校验;`market:check` 检查外部 DSH 市场 PR 与线上目录;`dev:ui` 启动不含真实凭据的本地 mock 市场。CI 使用 `--legacy-peer-deps` 安装显式测试依赖,DSH 运行期 peer 仍由 Host 提供。`v*` Tag 会触发 GitHub Actions;Tag 必须与 `package.json` 版本一致。Release 通过 npm Trusted Publishing (GitHub OIDC) 发布,不依赖长期 `NPM_TOKEN`。
107
142
 
108
143
  公共 Registry 每次合并后会生成 `catalog-stats.json`;本仓库的定时工作流每小时同步中英文介绍和统计快照。npm 页面中的静态正文随版本发布更新,上方动态统计徽标则直接读取 Registry,可在不发布新 npm 版本时保持实时数量一致。
109
144
 
110
- 当前公开版本为 [`dsh-mcp-connector@0.2.24`](https://www.npmjs.com/package/dsh-mcp-connector),对应 [GitHub Release v0.2.24](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.24)。
145
+ 当前公开版本为 [`dsh-mcp-connector@0.2.28`](https://www.npmjs.com/package/dsh-mcp-connector),对应 [GitHub Release v0.2.28](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.28)。
111
146
 
112
147
  版本能力与变更记录见 [CHANGELOG.md](CHANGELOG.md)。
113
148
  Desktop 发版回归见 [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md)。
@@ -117,7 +152,7 @@ stdio 传输的架构、透传边界与安全约束见 [docs/STDIO-SUPPORT.md](d
117
152
 
118
153
  ## 安全与限制
119
154
 
120
- - 凭证只持久化在 DSH storage domain,不进入目录、Git 仓库或对话历史。
155
+ - 凭证只持久化在本机 DSH storage 边界:连接记录使用 storage domain,OAuth 轮换凭据同步保存到 `$DSH_HOME/storages/mcp_connector_grants_v1`。该目录为 0700、文件为 0600;凭证不进入市场目录、Git 仓库、页面、日志或对话历史。
121
156
  - 市场 Key/Token 校验失败时不写入 storage domain;鉴权、超时、DNS、TLS/网络错误会分类提示。
122
157
  - 外部 URL 仅允许 HTTPS,HTTP 仅允许回环地址;导入配置会校验 URL 与 Header。
123
158
  - 远程目录/描述响应限制 2 MiB,Web API 请求限制 1 MiB;原始 JSON 在归一化前扫描凭据字段。
@@ -14,7 +14,7 @@ MCP连接器把“刷新连接器目录”和“升级插件代码”视为两
14
14
 
15
15
  每个 Provider 适配器只硬编码自己的 capabilities 入口,其余更新、任务、回滚和重启端点均从能力响应中读取。客户端只接受当前 DSH 页面的相对同源端点,拒绝跨域 URL、协议不匹配、缺少必要端点或未声明 `features.update` 的 Provider。
16
16
 
17
- DSH Market 适配器从 `GET /dsh-market/api/v1/capabilities` 开始,要求 `schema: dsh-market/update-api/v1` 与 `apiVersion: 1`。未安装任何可用 Provider、接口返回 404、协议不兼容或检查失败时,页面不会尝试私有更新路由,而是回退到“前往插件市场”;设置入口不可用时再打开 npm 包页面。
17
+ DSH Market 适配器从 `GET /dsh-market/api/v1/capabilities` 开始,要求 `schema: dsh-market/update-api/v1` 与 `apiVersion: 1`。未安装任何可用 Provider、接口返回 404、协议不兼容或检查失败时,页面不会尝试私有更新路由,而是显示“查看更新方式”。页面会先尝试打开当前宿主的插件市场;设置入口不可用,或 DSH Desktop 设置中实际没有插件市场分区时,打开 npm 包页面与安装命令说明。
18
18
 
19
19
  ## 更新流程
20
20
 
@@ -42,7 +42,7 @@ dsh web
42
42
  - **已安装**:查看已经保存到本机的连接,页签徽标显示连接数。
43
43
  - **搜索**:按名称、服务商、简介和标签查找。
44
44
  - **添加连接**:导入 JSON、手动配置 HTTP/stdio,或从市场描述 URL 安装。
45
- - **版本与更新提示**:标题旁显示当前插件版本,版本检查不依赖安装所用的插件市场。检测到 npm 有新版本且当前宿主存在兼容 Update Provider 时,可在当前页面一键更新并查看进度、失败原因和可用回滚。DSH Market API v1 是当前首个 Provider;无可用 Provider 时自动回退为“前往插件市场”或 npm 页面。
45
+ - **版本与更新提示**:标题旁显示当前插件版本,版本检查不依赖安装所用的插件市场。检测到 npm 有新版本且当前宿主存在兼容 Update Provider 时,可在当前页面一键更新并查看进度、失败原因和可用回滚。DSH Market API v1 是当前首个 Provider;无可用 Provider 时显示“查看更新方式”,宿主没有插件市场分区时会打开 npm 安装说明。
46
46
  - **刷新连接器目录**:重新拉取 Registry 卡片,并更新连接健康状态;该操作不会升级 MCP连接器插件本身。
47
47
 
48
48
  ## 3. 浏览市场与分类
@@ -75,7 +75,7 @@ stdio 市场卡片也可能显示一个或多个凭据字段,例如 API Token
75
75
 
76
76
  OAuth 一键连接要求服务商支持标准 OAuth 2.1/PKCE 和公开元数据发现。动态客户端注册既支持无需客户端密钥的 `none`,也支持服务商签发密钥的 `client_secret_post` 与 `client_secret_basic`。客户端密钥仅与 OAuth Grant 一同保存在 DSH 本机,用于换取、刷新和撤销 Token;插件不会要求用户把 OAuth Token 或客户端密钥复制到聊天中。
77
77
 
78
- 当市场描述声明 `grantSharing: "issuer"` 时,同一账号下相同 issuer、scope 和客户端鉴权方式的卡片共享一组 Grant。首次授权仍只启用用户点击的卡片;之后连接同组卡片会直接复用现有授权,不再重复打开 OAuth 页面。升级时会验证并自动归并旧版本留下的同 issuer 多份 Grant;多个 DSH 进程并发轮换 Refresh Token 时也会重读较新的本机持久化凭据后自动恢复。网络、OAuth 元数据发现或服务端 5xx 等暂时故障只进入“自动重试中”,确认本机也没有其他进程保存的新 Token,且明确收到 Refresh Token/客户端失效错误时,才进入“需重新授权”。
78
+ 当市场描述声明 `grantSharing: "issuer"` 时,同一账号下相同 issuer、scope 和客户端鉴权方式的卡片共享一组 Grant。首次授权仍只启用用户点击的卡片;之后连接同组卡片会直接复用现有授权,不再重复打开 OAuth 页面。升级时会验证并自动归并旧版本留下的同 issuer 多份 Grant;Desktop `dsh web` 并发时,插件使用跨进程锁串行轮换,并从每 Grant 独立原子日志读取最新 Token,避免 DSH 整文件 storage 的旧进程快照覆盖新凭据。网络、OAuth 元数据发现或服务端 5xx 等暂时故障只进入“自动重试中”;确认 journal 中也没有更新 Token,且明确收到 Refresh Token/客户端失效错误时,才进入“需重新授权”。
79
79
 
80
80
  ## 5. 查看详情、Prompt 与工具
81
81
 
package/lib/client.js CHANGED
@@ -440,7 +440,8 @@ window.__ModuleLoader__.load({
440
440
 
441
441
  /**
442
442
  * DSH 暂未公开“打开指定设置分区”服务:只依赖稳定的 sidebar.settings
443
- * slot 和可访问文本打开插件市场,不依赖构建生成的 CSS 类名。
443
+ * slot 和可访问文本打开插件市场。Desktop 可能根本没有市场分区,
444
+ * 轮询结束后必须回退到 npm 安装说明,不能把用户留在普通设置页。
444
445
  */
445
446
  function openDshPluginMarket(actions) {
446
447
  const settingsHost = document.querySelector('[data-slot="sidebar.settings"]');
@@ -463,7 +464,11 @@ window.__ModuleLoader__.load({
463
464
  return;
464
465
  }
465
466
  attempts += 1;
466
- if (attempts < 20) window.setTimeout(selectMarket, 50);
467
+ if (attempts < 20) {
468
+ window.setTimeout(selectMarket, 50);
469
+ return;
470
+ }
471
+ window.open(NPM_PACKAGE_URL, "_blank", "noopener,noreferrer");
467
472
  };
468
473
  window.requestAnimationFrame(selectMarket);
469
474
  }, 0);
@@ -793,7 +798,7 @@ window.__ModuleLoader__.load({
793
798
  type: "button",
794
799
  className: "mcpConnectorUpdateButton mcpConnectorUpdateSecondary",
795
800
  onClick: () => openDshPluginMarket(actions),
796
- children: "打开插件市场"
801
+ children: "查看更新方式"
797
802
  })
798
803
  ]
799
804
  });
@@ -860,7 +865,7 @@ window.__ModuleLoader__.load({
860
865
  type: "button",
861
866
  className: "mcpConnectorUpdateButton mcpConnectorUpdateSecondary",
862
867
  onClick: () => openDshPluginMarket(actions),
863
- children: "打开插件市场"
868
+ children: "查看更新方式"
864
869
  })
865
870
  ]
866
871
  });
@@ -887,7 +892,7 @@ window.__ModuleLoader__.load({
887
892
  className: "mcpConnectorUpdateButton",
888
893
  title: `npm 已发布 v${versionStatus?.latestVersion}`,
889
894
  onClick: () => openDshPluginMarket(actions),
890
- children: `前往插件市场更新到 v${versionStatus?.latestVersion}`
895
+ children: `查看 v${versionStatus?.latestVersion} 更新方式`
891
896
  });
892
897
  };
893
898
  const src = window.location.origin + "/mcp-connector/ui/";
@@ -0,0 +1,192 @@
1
+ /**
2
+ * OAuth Grant 跨进程轮换日志。
3
+ *
4
+ * DSH JSON storage 以“每进程内存状态 + 整文件原子替换”实现,并不保证两个
5
+ * DSH Host 同时打开同一 domain 时的写入一致性。Refresh Token 又通常每次刷新都
6
+ * 会轮换,所以 Desktop 与 dsh web 并行时必须额外做跨进程串行化。
7
+ *
8
+ * 每个 Grant 独立文件,避免一个过期进程覆盖其他 Grant;文件与锁均位于
9
+ * $DSH_HOME/storages/mcp_connector_grants_v1,目录 0700、文件 0600。
10
+ */
11
+ import { createHash, randomUUID } from 'node:crypto';
12
+ import { chmod, mkdir, open, readFile, readdir, rename, rm, stat } from 'node:fs/promises';
13
+ import { homedir } from 'node:os';
14
+ import { dirname, join, resolve } from 'node:path';
15
+ import { grantRecordSchema } from './schema.js';
16
+
17
+ const JOURNAL_VERSION = 1;
18
+ const DEFAULT_LOCK_TIMEOUT_MS = 30_000;
19
+ const DEFAULT_STALE_LOCK_MS = 5 * 60_000;
20
+
21
+ function sleep(ms) {
22
+ return new Promise((done) => setTimeout(done, ms));
23
+ }
24
+
25
+ function fileId(grantKey) {
26
+ return createHash('sha256').update(grantKey).digest('hex');
27
+ }
28
+
29
+ export function defaultGrantJournalDir() {
30
+ if (process.env.NODE_TEST_CONTEXT) return null;
31
+ const dshHome = process.env.DSH_HOME?.trim() || join(homedir(), '.dsh');
32
+ return resolve(dshHome, 'storages', 'mcp_connector_grants_v1');
33
+ }
34
+
35
+ async function writeAtomic(path, document) {
36
+ const temp = join(dirname(path), `.${randomUUID()}.tmp`);
37
+ try {
38
+ const handle = await open(temp, 'wx', 0o600);
39
+ try {
40
+ await handle.writeFile(`${JSON.stringify(document, null, 2)}\n`, 'utf8');
41
+ await handle.sync();
42
+ } finally {
43
+ await handle.close();
44
+ }
45
+ await rename(temp, path);
46
+ await chmod(path, 0o600);
47
+ } catch (error) {
48
+ await rm(temp, { force: true }).catch(() => {});
49
+ throw error;
50
+ }
51
+ }
52
+
53
+ function parseDocument(text, path) {
54
+ let document;
55
+ try {
56
+ document = JSON.parse(text);
57
+ } catch (error) {
58
+ throw new Error(`OAuth grant journal is malformed: ${path}`, { cause: error });
59
+ }
60
+ if (document?.version !== JOURNAL_VERSION) {
61
+ throw new Error(`OAuth grant journal version is unsupported: ${path}`);
62
+ }
63
+ const parsed = grantRecordSchema.safeParse(document.grant);
64
+ if (!parsed.success) {
65
+ throw new Error(`OAuth grant journal record is invalid: ${path}`);
66
+ }
67
+ return parsed.data;
68
+ }
69
+
70
+ export class GrantJournal {
71
+ constructor({ rootDir = defaultGrantJournalDir(), logger } = {}) {
72
+ this.rootDir = rootDir ? resolve(rootDir) : null;
73
+ this.logger = logger;
74
+ }
75
+
76
+ get enabled() {
77
+ return this.rootDir !== null;
78
+ }
79
+
80
+ async ensureRoot() {
81
+ if (!this.rootDir) return;
82
+ await mkdir(this.rootDir, { recursive: true, mode: 0o700 });
83
+ await chmod(this.rootDir, 0o700);
84
+ }
85
+
86
+ recordPath(grantKey) {
87
+ return join(this.rootDir, `${fileId(grantKey)}.json`);
88
+ }
89
+
90
+ lockPath(grantKey) {
91
+ return join(this.rootDir, `${fileId(grantKey)}.lock`);
92
+ }
93
+
94
+ async get(grantKey) {
95
+ if (!this.rootDir) return undefined;
96
+ try {
97
+ return parseDocument(await readFile(this.recordPath(grantKey), 'utf8'), this.recordPath(grantKey));
98
+ } catch (error) {
99
+ if (error?.code === 'ENOENT') return undefined;
100
+ this.logger?.warn?.(`skip unreadable OAuth grant journal record ${fileId(grantKey)}: ${error.message}`);
101
+ return undefined;
102
+ }
103
+ }
104
+
105
+ async entries() {
106
+ if (!this.rootDir) return [];
107
+ await this.ensureRoot();
108
+ const names = await readdir(this.rootDir).catch((error) => {
109
+ if (error?.code === 'ENOENT') return [];
110
+ throw error;
111
+ });
112
+ const out = [];
113
+ for (const name of names.filter((item) => item.endsWith('.json'))) {
114
+ const path = join(this.rootDir, name);
115
+ try {
116
+ const grant = parseDocument(await readFile(path, 'utf8'), path);
117
+ out.push([grant.key, grant]);
118
+ } catch (error) {
119
+ this.logger?.warn?.(`skip unreadable OAuth grant journal record ${name}: ${error.message}`);
120
+ }
121
+ }
122
+ return out;
123
+ }
124
+
125
+ async put(grant) {
126
+ if (!this.rootDir) return grant;
127
+ await this.ensureRoot();
128
+ const parsed = grantRecordSchema.parse(grant);
129
+ await writeAtomic(this.recordPath(parsed.key), { version: JOURNAL_VERSION, grant: parsed });
130
+ return parsed;
131
+ }
132
+
133
+ async delete(grantKey) {
134
+ if (!this.rootDir) return false;
135
+ try {
136
+ await rm(this.recordPath(grantKey));
137
+ return true;
138
+ } catch (error) {
139
+ if (error?.code === 'ENOENT') return false;
140
+ throw error;
141
+ }
142
+ }
143
+
144
+ async withLock(grantKey, task, {
145
+ timeoutMs = DEFAULT_LOCK_TIMEOUT_MS,
146
+ staleLockMs = DEFAULT_STALE_LOCK_MS,
147
+ } = {}) {
148
+ if (!this.rootDir) return task();
149
+ await this.ensureRoot();
150
+ const path = this.lockPath(grantKey);
151
+ const startedAt = Date.now();
152
+ const owner = randomUUID();
153
+ let handle;
154
+ for (;;) {
155
+ try {
156
+ handle = await open(path, 'wx', 0o600);
157
+ try {
158
+ await handle.writeFile(JSON.stringify({ owner, pid: process.pid, createdAt: Date.now() }), 'utf8');
159
+ await handle.sync();
160
+ } catch (error) {
161
+ await handle.close().catch(() => {});
162
+ await rm(path, { force: true }).catch(() => {});
163
+ throw error;
164
+ }
165
+ break;
166
+ } catch (error) {
167
+ if (error?.code !== 'EEXIST') throw error;
168
+ const lockStat = await stat(path).catch(() => null);
169
+ if (lockStat && Date.now() - lockStat.mtimeMs > staleLockMs) {
170
+ await rm(path, { force: true });
171
+ continue;
172
+ }
173
+ if (Date.now() - startedAt >= timeoutMs) {
174
+ const timeout = new Error('timed out waiting for another DSH process to finish OAuth refresh');
175
+ timeout.code = 'refresh_lock_timeout';
176
+ throw timeout;
177
+ }
178
+ await sleep(100);
179
+ }
180
+ }
181
+ try {
182
+ return await task();
183
+ } finally {
184
+ await handle?.close().catch(() => {});
185
+ // 避免极端超时下旧持有者删掉已被新进程接管的锁(ABA)。
186
+ const current = await readFile(path, 'utf8').catch(() => '');
187
+ if (current.includes(`"owner":"${owner}"`)) {
188
+ await rm(path, { force: true }).catch(() => {});
189
+ }
190
+ }
191
+ }
192
+ }
package/lib/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * 能力:
5
5
  * - 连接器目录(内置 + 远程 registry + config 注入 + 本地上下架覆盖)
6
6
  * - 三通道连接:通用 OAuth(PKCE) / 自定义配置 / 粘贴 JSON / URL 安装
7
- * - 连接记录与 OAuth grant 持久化(ctx.storageDomain),重启恢复
7
+ * - 连接记录与 OAuth grant 持久化,重启恢复与跨 Host Token 轮换
8
8
  * - 通过 ctx.loader 动态配置 @deepseek-ai/dsh-mcp-client 条目
9
9
  * - 对话工具:mcp_connector_catalog / connect / configure / import_json /
10
10
  * install_from_url / status / set_enabled / disconnect / refresh_catalog / publish
@@ -95,7 +95,12 @@ export async function apply(ctx, config) {
95
95
  });
96
96
 
97
97
  const connectionStore = new ConnectionStore(domain);
98
- const grantStore = new GrantStore(domain);
98
+ const grantStore = new GrantStore(domain, {
99
+ // 只有真实持久化模式才启用跨进程 Grant journal。测试可通过内部字段
100
+ // 传入临时目录,不让 fake storage 触碰用户真实 $DSH_HOME。
101
+ journalDir: config.persistSecrets ? config.__grantJournalDir : null,
102
+ logger,
103
+ });
99
104
  const catalogStore = new CatalogStore(domain);
100
105
 
101
106
  const state = {
@@ -382,7 +387,27 @@ export async function apply(ctx, config) {
382
387
  async function refreshGrantWithRecovery(grantKey, phase) {
383
388
  const observed = state.grants.get(grantKey)?.grant;
384
389
  try {
385
- return await refreshGrant(grantKey);
390
+ return await grantStore.withRefreshLock(grantKey, async () => {
391
+ // 锁内必须从独立 journal 重读,不能依赖 DSH domain 的进程内缓存。
392
+ // 如果另一 Host 已完成轮换,直接采用新 Grant,避免再次消耗
393
+ // 一次性 Refresh Token。
394
+ const persisted = await grantStore.get(grantKey).catch(() => undefined);
395
+ const entry = state.grants.get(grantKey);
396
+ if (entry && persisted
397
+ && persisted.updatedAt > entry.grant.updatedAt
398
+ && persisted.refreshToken !== entry.grant.refreshToken) {
399
+ entry.grant = persisted;
400
+ entry.needsReauth = false;
401
+ entry.refreshFailureKind = null;
402
+ entry.lastRefreshError = null;
403
+ logger.info(`OAuth grant ${grantKey} adopted a newer cross-process journal token`);
404
+ if (persisted.accessTokenExpiresAt > Date.now()) {
405
+ scheduleRefresh(grantKey);
406
+ return persisted;
407
+ }
408
+ }
409
+ return refreshGrant(grantKey);
410
+ }, { timeoutMs: Math.max(30_000, config.requestTimeoutMs * 2) });
386
411
  } catch (error) {
387
412
  let finalError = error;
388
413
  const initialFailure = classifyRefreshFailure(error);
@@ -405,7 +430,13 @@ export async function apply(ctx, config) {
405
430
  entry.lastRefreshError = null;
406
431
  logger.info(`OAuth grant ${grantKey} adopted a newer persisted token after a concurrent rotation`);
407
432
  try {
408
- return await refreshGrant(grantKey);
433
+ if (persisted.accessTokenExpiresAt > Date.now()) {
434
+ scheduleRefresh(grantKey);
435
+ return persisted;
436
+ }
437
+ return await grantStore.withRefreshLock(grantKey, () => refreshGrant(grantKey), {
438
+ timeoutMs: Math.max(30_000, config.requestTimeoutMs * 2),
439
+ });
409
440
  } catch (retryError) {
410
441
  finalError = retryError;
411
442
  }
package/lib/stores.js CHANGED
@@ -1,11 +1,13 @@
1
1
  /**
2
2
  * 存储域封装:一个 domain 三张表(connections / grants / catalog)。
3
- * 落盘 ~/.dsh/storages/(0700),凭证不进入 loader 配置树以外的任何地方。
3
+ * OAuth grant 同步写入本机受限权限 journal,防止多 Host 的整文件
4
+ * last-write-wins 覆盖新轮换 Token;凭证不进入 loader 配置树或日志。
4
5
  */
5
6
  import { defineDomain, domainTable } from '@deepseek-ai/dsh-storage-domain';
6
7
  import { connectionRecordSchema, grantRecordSchema, catalogRecordSchema } from './schema.js';
7
8
  import { DEFAULT_ACCOUNT } from './constants.js';
8
9
  import { shortHash } from './util.js';
10
+ import { GrantJournal } from './grant-journal.js';
9
11
 
10
12
  export function defineConnectorDomain() {
11
13
  return defineDomain({
@@ -49,9 +51,10 @@ export class ConnectionStore {
49
51
 
50
52
  /** OAuth 授权表(通用多 issuer / 多账号) */
51
53
  export class GrantStore {
52
- constructor(domain) {
54
+ constructor(domain, { journalDir, journal, logger } = {}) {
53
55
  this.table = domain.tables.get('grants');
54
56
  if (!this.table) throw new Error('mcp-connector: domain table "grants" not found');
57
+ this.journal = journal ?? new GrantJournal({ rootDir: journalDir, logger });
55
58
  }
56
59
 
57
60
  keyFor(account, issuer, clientId, scope) {
@@ -59,23 +62,42 @@ export class GrantStore {
59
62
  }
60
63
 
61
64
  async get(key) {
62
- return this.table.get(key);
65
+ const legacy = await this.table.get(key);
66
+ const journal = await this.journal.get(key);
67
+ if (!legacy) return journal;
68
+ if (!journal) return legacy;
69
+ return journal.updatedAt >= legacy.updatedAt ? journal : legacy;
63
70
  }
64
71
 
65
72
  async put(grant) {
66
73
  const next = { ...grant, updatedAt: Date.now() };
74
+ // Journal 先落盘:即使 DSH JSON domain 被另一进程的旧内存整文件覆盖,
75
+ // 下次刷新仍能从独立记录取回最新的轮换 Token。
76
+ await this.journal.put(next);
67
77
  await this.table.put(grant.key, next);
68
78
  return next;
69
79
  }
70
80
 
71
81
  async delete(key) {
72
- return this.table.delete(key);
82
+ const [journalDeleted, tableDeleted] = await Promise.all([
83
+ this.journal.delete(key),
84
+ this.table.delete(key),
85
+ ]);
86
+ return journalDeleted || tableDeleted;
73
87
  }
74
88
 
75
89
  async entries() {
76
- const out = [];
77
- for (const [key, value] of this.table.entries()) out.push([key, value]);
78
- return out;
90
+ const merged = new Map();
91
+ for (const [key, value] of this.table.entries()) merged.set(key, value);
92
+ for (const [key, value] of await this.journal.entries()) {
93
+ const current = merged.get(key);
94
+ if (!current || value.updatedAt >= current.updatedAt) merged.set(key, value);
95
+ }
96
+ return [...merged.entries()];
97
+ }
98
+
99
+ async withRefreshLock(key, task, options) {
100
+ return this.journal.withLock(key, task, options);
79
101
  }
80
102
  }
81
103
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-mcp-connector",
3
- "version": "0.2.26",
4
- "description": "General-purpose MCP connector, connection manager, plugin extension, and integration marketplace for DeepSeek Harness, initiated and maintained by Qichacha/QCC.",
3
+ "version": "0.2.28",
4
+ "description": "Connect and manage MCP servers in DeepSeek Harness — a universal MCP connector and marketplace with OAuth 2.0 PKCE, API keys, stdio/HTTP, mcpServers JSON import, tools, and prompts. Maintained by Qichacha/QCC.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -25,6 +25,7 @@
25
25
  "cordis.patch.yml",
26
26
  "README.md",
27
27
  "README.en.md",
28
+ "CONTRIBUTING.md",
28
29
  "CHANGELOG.md",
29
30
  "LICENSE",
30
31
  "install.sh",
@@ -34,18 +35,21 @@
34
35
  ],
35
36
  "scripts": {
36
37
  "test": "node --test",
37
- "lint": "node --check lib/index.js && node --check lib/web.js && node --check lib/client.js && node --check lib/version-status.js && node --check lib/schema.js && node --check lib/catalog.js && node --check lib/prompts.js && node --check lib/probe.js && node --check lib/mcp-validation.js && node --check lib/mcp-http.js && node --check lib/migration.js && node --check lib/stores.js && node --check lib/oauth.js && node --check lib/grant-lifecycle.js && node --check lib/callback-server.js && node --check lib/mcp-provision.js && node --check lib/util.js && node --check lib/constants.js && node --check lib/tools.js && node --check lib/connectors/oauth-connector.js && node --check lib/connectors/manual-connector.js && node --check lib/connectors/json-connector.js && node --check scripts/probe-connector.mjs && node --check scripts/build-registry.mjs && node --check scripts/ui-harness.mjs && node --check scripts/check-market-registration.mjs && node --check scripts/sync-registry-stats.mjs",
38
+ "lint": "node --check lib/index.js && node --check lib/web.js && node --check lib/client.js && node --check lib/version-status.js && node --check lib/schema.js && node --check lib/catalog.js && node --check lib/prompts.js && node --check lib/probe.js && node --check lib/mcp-validation.js && node --check lib/mcp-http.js && node --check lib/migration.js && node --check lib/stores.js && node --check lib/grant-journal.js && node --check lib/oauth.js && node --check lib/grant-lifecycle.js && node --check lib/callback-server.js && node --check lib/mcp-provision.js && node --check lib/util.js && node --check lib/constants.js && node --check lib/tools.js && node --check lib/connectors/oauth-connector.js && node --check lib/connectors/manual-connector.js && node --check lib/connectors/json-connector.js && node --check scripts/probe-connector.mjs && node --check scripts/build-registry.mjs && node --check scripts/ui-harness.mjs && node --check scripts/check-market-registration.mjs && node --check scripts/check-readme-version.mjs && node --check scripts/sync-registry-stats.mjs",
38
39
  "registry:build": "node scripts/build-registry.mjs",
39
40
  "registry:validate": "node scripts/probe-connector.mjs catalog/catalog.json --validate-only",
40
41
  "dev:ui": "node scripts/ui-harness.mjs",
41
42
  "market:check": "node scripts/check-market-registration.mjs",
42
43
  "registry:stats:sync": "node scripts/sync-registry-stats.mjs",
44
+ "docs:check": "node scripts/check-readme-version.mjs",
43
45
  "verify-pack": "node scripts/verify-pack.mjs",
44
- "check": "npm run lint && npm test && npm run verify-pack",
46
+ "check": "npm run lint && npm run docs:check && npm test && npm run verify-pack",
45
47
  "prepublishOnly": "npm run check"
46
48
  },
47
49
  "keywords": [
48
50
  "deepseek-harness",
51
+ "deepseek",
52
+ "harness",
49
53
  "dsh",
50
54
  "dsh-plugin",
51
55
  "cordis",
@@ -54,6 +58,14 @@
54
58
  "mcp-server",
55
59
  "mcp-connector",
56
60
  "mcp-marketplace",
61
+ "model-context-protocol",
62
+ "mcp-hub",
63
+ "mcp-manager",
64
+ "mcp-registry",
65
+ "mcp-tools",
66
+ "mcp-store",
67
+ "remote-mcp",
68
+ "streamable-http",
57
69
  "connector",
58
70
  "connection",
59
71
  "connection-manager",
@@ -64,6 +76,8 @@
64
76
  "agent-skills",
65
77
  "skill-extension",
66
78
  "ai-tools",
79
+ "llm-tools",
80
+ "ai-agents",
67
81
  "oauth",
68
82
  "oauth2",
69
83
  "pkce",