dsh-mcp-connector 0.2.18 → 0.2.20

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,39 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.2.20] - 2026-08-25
8
+
9
+ ### Changed
10
+
11
+ - 默认公共 Registry 改用 jsDelivr CDN,并在主源请求失败时按顺序回退到 GitHub Raw,提升不同网络环境下远程市场目录的可用性。
12
+ - DSH Bundle 补丁与插件默认配置统一使用 jsDelivr 主源;用户显式配置的自定义目录地址保持单一来源,不会被公共备用源覆盖。
13
+
14
+ ### Fixed
15
+
16
+ - 主源与备用源分别处理缓存协商;备用源不会复用或写入主源 ETag,避免跨源缓存标识导致错误的 `304 Not Modified`。
17
+ - 目录主源失败时记录逐源告警,并在备用源成功后继续刷新市场,避免一次网络故障导致远程目录完全不可用。
18
+
19
+ ### Verification
20
+
21
+ - 插件 98 项自动测试、lint、npm 发布文件白名单/敏感内容扫描和 `git diff --check` 通过。
22
+ - 新增主源失败回退、主源 `304` 不访问备用源、默认配置与 Bundle 配置一致,以及自定义目录不启用公共回退的回归测试。
23
+
24
+ ## [0.2.19] - 2026-08-24
25
+
26
+ ### Fixed
27
+
28
+ - 所有对话工具结果在交给 DSH Host 前统一转换为 lossless JSON,清理 `undefined`、非有限数字、`BigInt`、稀疏数组和循环引用,修复 Windows DSH Desktop 的 `value is not lossless JSON` 错误。
29
+ - UI 静态文件路由不再使用平台相关的 `path.normalize` 处理 URL,Windows 下保持正斜杠语义并继续拒绝路径穿越,修复市场 iframe 返回 404。
30
+
31
+ ### Documentation
32
+
33
+ - 中英文 README 更新为公共 Registry 61 条、与 4 张随包卡片合并后 65 张市场卡片的当前规模;重新采集市场总览并重建 16 秒演示 GIF。
34
+
35
+ ### Verification
36
+
37
+ - 插件 94 项自动测试、lint、npm 发布文件白名单/敏感内容扫描和 `git diff --check` 通过。
38
+ - 手动执行最新 Registry `main` 的 61 项健康巡检:60 项通过、1 项部分通过;44 个远程 MCP Server 全部可达并识别成功,28 个 stdio Server 按安全策略未执行本地命令。
39
+
7
40
  ## [0.2.18] - 2026-08-24
8
41
 
9
42
  ### Added
@@ -315,7 +348,9 @@
315
348
  - 外部 URL 与导入 Header 执行安全校验。
316
349
  - iframe 消息校验同源和消息来源。
317
350
 
318
- [Unreleased]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.18...HEAD
351
+ [Unreleased]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.20...HEAD
352
+ [0.2.20]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.19...v0.2.20
353
+ [0.2.19]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.18...v0.2.19
319
354
  [0.2.18]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.17...v0.2.18
320
355
  [0.2.17]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.16...v0.2.17
321
356
  [0.2.16]: https://github.com/duhu2000/dsh-mcp-connector/compare/v0.2.15...v0.2.16
package/README.en.md CHANGED
@@ -28,7 +28,7 @@ Browse and install MCP connectors from different providers in DeepSeek Harness D
28
28
  - A standalone remote Registry, allowing new marketplace cards to appear after refresh without publishing a new npm version.
29
29
  - Explicit, non-destructive migration of authorization from the two earlier Qichacha OAuth plugins.
30
30
 
31
- The first four bundled cards are Qichacha connectors, followed by PKULaw and Wind. After a remote Registry refresh, Yingmi, QVeris, Bazhuayu Cloud Collection, GitHub, Cloudflare, Notion, Tavily, and the Ace Data Cloud-hosted Seedream connector are also available, for 14 published marketplace cards after deduplication. Recommendations are limited to the four Qichacha cards, PKULaw, and Wind; all other connectors remain discoverable in their business categories.
31
+ As of August 24, 2026, the public Registry publishes 61 connector descriptors. After merging and deduplicating them with the four bundled Qichacha cards, the Marketplace exposes 65 cards across nine business categories. Recommendations remain limited to the four Qichacha cards, PKULaw, and Wind, for six featured cards in total. The Registry can evolve independently, so the badge shown after a client refresh is the authoritative current count.
32
32
 
33
33
  ## Interface and demo
34
34
 
@@ -71,7 +71,7 @@ The detailed [Chinese user guide](docs/USER-GUIDE.md) covers category browsing,
71
71
 
72
72
  ## Connector catalog
73
73
 
74
- The package contains a bundled fallback catalog. By default, it refreshes from the public [dsh-mcp-connector-registry](https://github.com/duhu2000/dsh-mcp-connector-registry); cached or bundled data remains available if the remote registry cannot be reached.
74
+ 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.
75
75
 
76
76
  The public connector registration process and descriptor requirements are documented in [docs/MARKET-REGISTRATION.md](docs/MARKET-REGISTRATION.md).
77
77
  The stdio architecture, passthrough boundary, and security constraints are documented in [docs/STDIO-SUPPORT.md](docs/STDIO-SUPPORT.md).
@@ -84,14 +84,14 @@ The default bundle configuration is in `cordis.patch.yml`:
84
84
  - id: mcp-connector
85
85
  name: dsh-mcp-connector
86
86
  config:
87
- catalogUrl: 'https://raw.githubusercontent.com/duhu2000/dsh-mcp-connector-registry/main/catalog.json'
87
+ catalogUrl: 'https://cdn.jsdelivr.net/gh/duhu2000/dsh-mcp-connector-registry@main/catalog.json'
88
88
  persistSecrets: true
89
89
  entryPrefix: mcp
90
90
  refreshSkewMs: 300000
91
91
  openBrowser: true
92
92
  ```
93
93
 
94
- Set `catalogUrl` to an empty string for an explicitly offline/private setup.
94
+ Set `catalogUrl` to an empty string for an explicitly offline/private setup. A custom non-default URL is used as-is and does not fall back to the public registry.
95
95
 
96
96
  ## Development and release checks
97
97
 
@@ -105,7 +105,7 @@ npm run dev:ui
105
105
 
106
106
  `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`.
107
107
 
108
- The current public version is [`dsh-mcp-connector@0.2.18`](https://www.npmjs.com/package/dsh-mcp-connector), with [GitHub Release v0.2.18](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.18).
108
+ The current public version is [`dsh-mcp-connector@0.2.20`](https://www.npmjs.com/package/dsh-mcp-connector), with [GitHub Release v0.2.20](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.20).
109
109
 
110
110
  See [CHANGELOG.md](CHANGELOG.md) for version history and [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md) for the Desktop release checklist.
111
111
 
package/README.md CHANGED
@@ -30,7 +30,7 @@
30
30
  - 平滑迁移:显式扫描并复制两个旧企查查 OAuth 插件授权;不删除原插件或原凭据。
31
31
  - 对话工具:`mcp_connector_catalog`、`connect`、`configure`、`import_json`、`install_from_url`、`status`、`health_check`、`set_enabled`、`disconnect`、`refresh_catalog`、`publish`、`tools_list`。
32
32
 
33
- 随包目录前 4 张为企查查连接器,另含“北大法宝·法律检索”和“Wind·股票数据”。远程 Registry 刷新后还会加入“盈米·基金投顾”、“QVerisMCP”、“八爪鱼·云采集”、GitHub、Cloudflare、Notion、Tavily 和由 Ace Data Cloud 托管的 Seedream,合并去重后当前共 14 张已发布市场卡片。推荐位严格保留 4 张企查查卡片、北大法宝和 Wind;其他连接器按业务分类展示。
33
+ 截至 2026-08-24,公共 Registry 已发布 61 条连接器描述;与随包的 4 张企查查卡片合并去重后,市场页可浏览 65 张卡片,覆盖企业数据、金融投资、法律合规、开发工具、办公协作、调研分析、设计创意、效率工具和其他 9 类。推荐位严格保留 4 张企查查卡片、北大法宝和 Wind,共 6 张;其他连接器按业务分类展示。Registry 可独立持续更新,实际数量以客户端刷新后的市场页签徽标为准。
34
34
 
35
35
  ## 界面与演示
36
36
 
@@ -79,14 +79,14 @@ Bundle 默认配置位于 `cordis.patch.yml`:
79
79
  - id: mcp-connector
80
80
  name: dsh-mcp-connector
81
81
  config:
82
- catalogUrl: 'https://raw.githubusercontent.com/duhu2000/dsh-mcp-connector-registry/main/catalog.json'
82
+ catalogUrl: 'https://cdn.jsdelivr.net/gh/duhu2000/dsh-mcp-connector-registry@main/catalog.json'
83
83
  persistSecrets: true
84
84
  entryPrefix: mcp
85
85
  refreshSkewMs: 300000
86
86
  openBrowser: true
87
87
  ```
88
88
 
89
- `catalogUrl` 默认指向公共 [dsh-mcp-connector-registry](https://github.com/duhu2000/dsh-mcp-connector-registry),支持 ETag/TTL 缓存;拉取失败时继续使用上次缓存或随包内置目录。需要离线/私有模式时可将其显式设为空字符串。
89
+ `catalogUrl` 默认通过 jsDelivr CDN 读取公共 [dsh-mcp-connector-registry](https://github.com/duhu2000/dsh-mcp-connector-registry),支持 ETag/TTL 缓存;主源失败时自动尝试 GitHub raw 备用源,再回退到上次缓存或随包内置目录。jsDelivr 的分支 URL 可能存在缓存延迟,因此 Registry 合并后的新卡片不保证秒级出现。需要离线/私有模式时可将 `catalogUrl` 显式设为空字符串;显式配置其他目录 URL 时不会自动切换到公共备用源。
90
90
 
91
91
  ## 开发与发布门禁
92
92
 
@@ -100,7 +100,7 @@ npm run dev:ui
100
100
 
101
101
  `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`。
102
102
 
103
- 当前公开版本为 [`dsh-mcp-connector@0.2.18`](https://www.npmjs.com/package/dsh-mcp-connector),对应 [GitHub Release v0.2.18](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.18)。
103
+ 当前公开版本为 [`dsh-mcp-connector@0.2.20`](https://www.npmjs.com/package/dsh-mcp-connector),对应 [GitHub Release v0.2.20](https://github.com/duhu2000/dsh-mcp-connector/releases/tag/v0.2.20)。
104
104
 
105
105
  版本能力与变更记录见 [CHANGELOG.md](CHANGELOG.md)。
106
106
  Desktop 发版回归见 [docs/DESKTOP-E2E.md](docs/DESKTOP-E2E.md)。
package/cordis.patch.yml CHANGED
@@ -5,7 +5,7 @@
5
5
  - id: mcp-connector
6
6
  name: 'dsh-mcp-connector'
7
7
  config:
8
- catalogUrl: 'https://raw.githubusercontent.com/duhu2000/dsh-mcp-connector-registry/main/catalog.json'
8
+ catalogUrl: 'https://cdn.jsdelivr.net/gh/duhu2000/dsh-mcp-connector-registry@main/catalog.json'
9
9
  persistSecrets: true
10
10
  entryPrefix: 'mcp'
11
11
  refreshSkewMs: 300000
@@ -25,7 +25,7 @@ Bearer/API Key 型连接器可在卡片上点「配置」,一次填写凭据
25
25
  3. 提交 PR。CI 会检查 Schema、重复 id/serverName、密钥、URL、MCP initialize、OAuth 元数据和图标。
26
26
  4. 合并后 CI 重建根目录 `catalog.json`;客户端点击“刷新”后可见,无需重新发布 `dsh-mcp-connector` npm 包。
27
27
 
28
- 插件默认读取 `https://raw.githubusercontent.com/duhu2000/dsh-mcp-connector-registry/main/catalog.json`;远程不可用时自动回退上次缓存或随包内置目录。
28
+ 插件默认通过 `https://cdn.jsdelivr.net/gh/duhu2000/dsh-mcp-connector-registry@main/catalog.json` 读取目录;主源失败时依次尝试 GitHub raw 备用源、上次缓存和随包内置目录。jsDelivr 的分支 URL 存在缓存延迟,合并后的新卡片可能不会秒级出现。
29
29
 
30
30
  ## 4. DSH 外部插件市场验收
31
31
 
package/lib/catalog.js CHANGED
@@ -134,6 +134,28 @@ export async function fetchRemoteCatalog(catalogUrl, { requestTimeoutMs, etag }
134
134
  }
135
135
  }
136
136
 
137
+ /**
138
+ * 按顺序拉取远程 registry。仅主源使用缓存 ETag;备用源总是全量拉取,
139
+ * 且不返回备用源 ETag,避免下一次请求把它误发给主源。
140
+ */
141
+ export async function fetchRemoteCatalogWithFallback(catalogUrl, fallbackUrls = [], opts = {}) {
142
+ const urls = [catalogUrl, ...fallbackUrls].filter(Boolean);
143
+ let lastError;
144
+ for (let index = 0; index < urls.length; index += 1) {
145
+ try {
146
+ const fetched = await fetchRemoteCatalog(urls[index], {
147
+ requestTimeoutMs: opts.requestTimeoutMs,
148
+ etag: index === 0 ? opts.etag : undefined,
149
+ });
150
+ return index === 0 ? fetched : { ...fetched, etag: undefined };
151
+ } catch (error) {
152
+ lastError = error;
153
+ opts.onSourceError?.({ index, error });
154
+ }
155
+ }
156
+ throw lastError ?? new Error('catalog fetch failed for all sources');
157
+ }
158
+
137
159
  /** 合并多层来源(sources 为低→高优先级的 descriptor 数组),再应用本地覆盖。 */
138
160
  export function mergeCatalog(sources, overrides = new Map()) {
139
161
  const merged = new Map();
package/lib/constants.js CHANGED
@@ -16,9 +16,16 @@ export const JSON_CONNECTOR_ID = '__json__';
16
16
  /** 保留 id 前缀:社区 PR 注册表拒绝占用 */
17
17
  export const RESERVED_ID_PREFIXES = ['qcc-', 'dsh-', 'mcp-connector-'];
18
18
 
19
- /** 请求 / 刷新默认值 */
19
+ /** 远程 registry 主目录源(jsDelivr CDN,国内网络更稳定) */
20
20
  export const DEFAULT_CATALOG_URL =
21
- 'https://raw.githubusercontent.com/duhu2000/dsh-mcp-connector-registry/main/catalog.json';
21
+ 'https://cdn.jsdelivr.net/gh/duhu2000/dsh-mcp-connector-registry@main/catalog.json';
22
+
23
+ /** 默认目录源失败时依次尝试;仅用于未自定义 catalogUrl 的配置。 */
24
+ export const DEFAULT_CATALOG_FALLBACK_URLS = [
25
+ 'https://raw.githubusercontent.com/duhu2000/dsh-mcp-connector-registry/main/catalog.json',
26
+ ];
27
+
28
+ /** 请求 / 刷新默认值 */
22
29
  export const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
23
30
  export const DEFAULT_REFRESH_SKEW_MS = 300_000;
24
31
  export const DEFAULT_CATALOG_TTL_MS = 3_600_000;
package/lib/index.js CHANGED
@@ -13,13 +13,14 @@ import z from '@deepseek-ai/schemastery';
13
13
  import {
14
14
  DEFAULT_ACCOUNT,
15
15
  DEFAULT_CATALOG_URL,
16
+ DEFAULT_CATALOG_FALLBACK_URLS,
16
17
  DEFAULT_REQUEST_TIMEOUT_MS,
17
18
  DEFAULT_REFRESH_SKEW_MS,
18
19
  DEFAULT_CATALOG_TTL_MS,
19
20
  } from './constants.js';
20
21
  import { defineConnectorDomain, ConnectionStore, GrantStore, CatalogStore } from './stores.js';
21
22
  import { normalizeConnectorDescriptor, normalizeConnectionRecord } from './schema.js';
22
- import { loadBundledCatalog, fetchRemoteCatalog, mergeCatalog, listCatalog, auditDescriptor, auditRawDescriptor, readLimitedJson } from './catalog.js';
23
+ import { loadBundledCatalog, fetchRemoteCatalogWithFallback, mergeCatalog, listCatalog, auditDescriptor, auditRawDescriptor, readLimitedJson } from './catalog.js';
23
24
  import { oauthAuthorize } from './connectors/oauth-connector.js';
24
25
  import { buildManualRecord } from './connectors/manual-connector.js';
25
26
  import { normalizeJsonImport } from './connectors/json-connector.js';
@@ -91,6 +92,15 @@ export async function apply(ctx, config) {
91
92
  state.merged = mergeCatalog(sources, state.overrides);
92
93
  }
93
94
 
95
+ function catalogFallbackUrls() {
96
+ return config.catalogUrl === DEFAULT_CATALOG_URL ? DEFAULT_CATALOG_FALLBACK_URLS : [];
97
+ }
98
+
99
+ function warnCatalogSourceFailure({ index, error }) {
100
+ const source = index === 0 ? 'primary' : `fallback ${index}`;
101
+ logger.warn(`catalog fetch ${source} failed: ${error instanceof Error ? error.message : String(error)}`);
102
+ }
103
+
94
104
  async function loadCatalog() {
95
105
  const dynamicRec = await catalogStore.getDynamic().catch(() => null);
96
106
  if (dynamicRec?.connectors) {
@@ -116,10 +126,15 @@ export async function apply(ctx, config) {
116
126
  if (config.catalogUrl) {
117
127
  const cached = await catalogStore.getRemote().catch(() => null);
118
128
  try {
119
- const fetched = await fetchRemoteCatalog(config.catalogUrl, {
120
- requestTimeoutMs: config.requestTimeoutMs,
121
- etag: cached?.etag,
122
- });
129
+ const fetched = await fetchRemoteCatalogWithFallback(
130
+ config.catalogUrl,
131
+ catalogFallbackUrls(),
132
+ {
133
+ requestTimeoutMs: config.requestTimeoutMs,
134
+ etag: cached?.etag,
135
+ onSourceError: warnCatalogSourceFailure,
136
+ },
137
+ );
123
138
  if (fetched.notModified) {
124
139
  state.remoteConnectors = cached?.connectors ?? [];
125
140
  } else {
@@ -816,7 +831,14 @@ export async function apply(ctx, config) {
816
831
  return { ok: true, message: `市场已刷新,共 ${visibleCount} 个连接器` };
817
832
  }
818
833
  try {
819
- const fetched = await fetchRemoteCatalog(config.catalogUrl, { requestTimeoutMs: config.requestTimeoutMs });
834
+ const fetched = await fetchRemoteCatalogWithFallback(
835
+ config.catalogUrl,
836
+ catalogFallbackUrls(),
837
+ {
838
+ requestTimeoutMs: config.requestTimeoutMs,
839
+ onSourceError: warnCatalogSourceFailure,
840
+ },
841
+ );
820
842
  state.remoteConnectors = fetched.connectors;
821
843
  await catalogStore.putRemote({ etag: fetched.etag, connectors: fetched.connectors }).catch(() => {});
822
844
  await recomputeMerged();
package/lib/tools.js CHANGED
@@ -61,9 +61,46 @@ const statusOutput = {
61
61
 
62
62
  const noArgs = { type: 'object', properties: {}, additionalProperties: false };
63
63
 
64
+ /**
65
+ * DSH tools require a lossless JSON value at the execute boundary. Normalize
66
+ * values that JSON.stringify would otherwise drop or rewrite, and always emit
67
+ * plain objects so transport-specific undefined fields cannot reject a tool
68
+ * result on stricter Desktop hosts.
69
+ */
70
+ export function toLossless(value, seen = new Set()) {
71
+ if (value === null) return null;
72
+ const type = typeof value;
73
+ if (type === 'string' || type === 'boolean') return value;
74
+ if (type === 'number') {
75
+ if (!Number.isFinite(value)) return null;
76
+ return Object.is(value, -0) ? 0 : value;
77
+ }
78
+ if (type === 'undefined' || type === 'function' || type === 'symbol' || type === 'bigint') return null;
79
+ if (type !== 'object' || seen.has(value)) return null;
80
+
81
+ seen.add(value);
82
+ let output;
83
+ if (Array.isArray(value)) {
84
+ output = [];
85
+ for (let index = 0; index < value.length; index += 1) {
86
+ output[index] = Object.hasOwn(value, index) ? toLossless(value[index], seen) : null;
87
+ }
88
+ } else {
89
+ output = {};
90
+ for (const key of Object.keys(value)) output[key] = toLossless(value[key], seen);
91
+ }
92
+ seen.delete(value);
93
+ return output;
94
+ }
95
+
64
96
  export function registerTools(ctx, api) {
65
97
  const disposers = [];
66
- const reg = (def) => disposers.push(ctx.tools.register(def));
98
+ const reg = (def) => disposers.push(ctx.tools.register({
99
+ ...def,
100
+ async execute(args, exec) {
101
+ return toLossless(await def.execute(args, exec));
102
+ },
103
+ }));
67
104
 
68
105
  reg({
69
106
  name: 'mcp_connector_catalog',
package/lib/web.js CHANGED
@@ -9,7 +9,7 @@
9
9
  * 本模块只依赖 ctx.webServer / ctx.webRuntime;在非 web 部署(无这两个服务)时
10
10
  * 调用方应跳过挂载,保证插件仍可用于纯对话工具场景。
11
11
  */
12
- import { dirname, join, normalize, relative, resolve } from 'node:path';
12
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
13
13
  import { readFile } from 'node:fs/promises';
14
14
  import { fileURLToPath } from 'node:url';
15
15
 
@@ -131,20 +131,26 @@ function extOf(pathname) {
131
131
  }
132
132
 
133
133
  /** 把 URL pathname 安全地映射到 ui 目录内的文件(防目录穿越)。 */
134
- function resolveUiFile(pathname) {
135
- let rel;
134
+ export function splitUiPath(pathname) {
135
+ let decoded;
136
136
  try {
137
- rel = decodeURIComponent(pathname);
137
+ decoded = decodeURIComponent(pathname);
138
138
  } catch {
139
- return { ok: false };
139
+ return undefined;
140
140
  }
141
- rel = normalize(rel.replace(/\\/g, '/')).replace(/^\/+/, '');
142
- if (rel === '.' || rel === '') return { ok: true, path: join(UI_DIR, 'index.html'), rel: 'index.html' };
143
- if (rel.startsWith('..')) return { ok: false };
144
- const abs = resolve(UI_DIR, rel);
141
+ const segments = decoded.replace(/\\/g, '/').split('/').filter((segment) => segment !== '' && segment !== '.');
142
+ if (segments.includes('..')) return undefined;
143
+ return segments;
144
+ }
145
+
146
+ export function resolveUiFile(pathname) {
147
+ const segments = splitUiPath(pathname);
148
+ if (segments === undefined) return { ok: false };
149
+ if (segments.length === 0) return { ok: true, path: join(UI_DIR, 'index.html'), rel: 'index.html' };
150
+ const abs = resolve(UI_DIR, ...segments);
145
151
  const relAbs = relative(UI_DIR, abs);
146
- if (relAbs.startsWith('..') || relAbs === '') return { ok: false };
147
- return { ok: true, path: abs, rel: relAbs };
152
+ if (relAbs === '..' || relAbs.startsWith(`..${sep}`) || isAbsolute(relAbs) || relAbs === '') return { ok: false };
153
+ return { ok: true, path: abs, rel: segments.join('/') };
148
154
  }
149
155
 
150
156
  /* ───────────────────────── 路由挂载 ───────────────────────── */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mcp-connector",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "description": "General-purpose MCP connector, connection manager, plugin extension, and integration marketplace for DeepSeek Harness, initiated and maintained by Qichacha/QCC.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",