@deepseek-ai/dsh-mcp-client 0.1.5-rc.2 → 0.1.6-alpha.2

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/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/mcp/mcp-client/README.md
5
- README.md: de646256888f1c271ad94a4c863338dfb02551e6
6
- README.zh.md: 2c71060ec309e823ff3456207881e27ab9aca553
5
+ README.md: a241dda5d77da5cea1d7092a99d670ead564ba03
6
+ README.zh.md: dbebdae8675aecf9c1107f53c9aeec9469def1df
package/README.md CHANGED
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- `dsh-mcp-client` lets the model call tools from external Model Context Protocol (MCP) servers as native harness tools. Configure one server per entry, and its tools appear under stable names such as `mcp__github__create_issue`. Use it for filesystem, GitHub, database, memory, or other MCP tool servers; no server is enabled by default. Tool definitions add tokens to every model request, while a slow or crashed server can delay startup or make its tools fail until recovery. The package bridges tools only; MCP resources and prompts are unsupported.
12
+ `dsh-mcp-client` lets the model use tools and resources from external Model Context Protocol (MCP) servers. Configure one server per entry; its tools use names such as `mcp__github__create_issue`. No server is enabled by default. Shipped profiles already provide [shared resource discovery and reading](../mcp-resources/README.md). An empty caller scope adds no MCP tools or prompt text. Server instructions join the logged system prompt as literal text; MCP prompt templates are unsupported. Slow or crashed servers can delay startup or fail calls until recovery.
13
13
 
14
14
  ## Table of Contents
15
15
 
@@ -25,7 +25,7 @@ English | [中文](README.zh.md)
25
25
  <a id="use-this-package"></a>
26
26
  ## Use this package
27
27
 
28
- Add `dsh-mcp-client` when the model should call tools from an external MCP server as if they were native. One configuration entry per server is the entire setup: give the server a short unique name and a transport, and its tools appear as `mcp__<serverName>__<tool>`. Choose stdio when the server runs as a local program and Streamable HTTP when it runs as a service. If you already use MCP tool servers from another client, the same server rows work here.
28
+ Add `dsh-mcp-client` when the model should call tools from an external MCP server as if they were native. Give each server a unique name and transport. The official SDK selects the 2026-07-28 protocol when available and falls back to supported legacy revisions. Choose stdio for a local program and Streamable HTTP for a service; stdio negotiation starts a temporary probe process before the serving process.
29
29
 
30
30
  ### Minimal configuration
31
31
 
@@ -58,7 +58,8 @@ Add one entry per server; nothing else is required. After the harness starts, th
58
58
  | `serverName` | required | Namespace for the server's tool names; `[A-Za-z0-9_-]{1,32}`, unique inside one registration scope |
59
59
  | `command` / `args` / `env` / `cwd` | — | stdio: executable, arguments, extra env merged over scrubbed ambient env, working directory |
60
60
  | `url` / `headers` | — | streamable-http: endpoint URL and extra request headers |
61
- | `toolCallTimeoutMs` | `60,000` | Timeout per `tools/call` invocation |
61
+ | `toolCallTimeoutMs` | `60,000` | Timeout per `tools/call` or resource request |
62
+ | `maxInstructionBytes` | `32,768` | Maximum UTF-8 bytes of server instructions including attribution; an oversized value rejects the connection |
62
63
  | `failOnStartupError` | `false` | Reject plugin activation when the initial connection or tool synchronization fails |
63
64
  | `reconnect.enabled` | `true` | Reconnect automatically after a lost connection |
64
65
  | `reconnect.initialDelayMs` | `500` | First reconnect delay; doubles per consecutive failed attempt |
@@ -67,7 +68,7 @@ Add one entry per server; nothing else is required. After the harness starts, th
67
68
 
68
69
  The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-mcp-client) is the exhaustive source for every accepted field.
69
70
 
70
- After startup, the server's tools appear as `mcp__<serverName>__<tool>` — try a prompt that uses one. If the initial connection fails, the harness still starts but no tools from that server appear, and an error is logged; set `failOnStartupError: true` to make a startup failure abort the harness instead.
71
+ After startup, the server's tools appear as `mcp__<serverName>__<tool>` — try a prompt that uses one. If the initial connection fails, the harness still starts but no tools from that server appear, and an error is logged. Setting `failOnStartupError: true` rejects plugin activation; [app-boot's startup policy](../../boot/app-boot/README.md) still permits an optional MCP entry to fail without aborting the harness.
71
72
 
72
73
  ### Tool naming and coexistence
73
74
 
@@ -76,7 +77,7 @@ The model sees each tool under a stable server-qualified name: `mcp__<serverName
76
77
  - Two servers publishing the same tool name (for example `search`) coexist under their own namespaces.
77
78
  - Two entries using the same server name: the later one fails to load with a clear error.
78
79
  - A server that lists the same tool twice gets its tool list rejected as invalid, and the previous tool set stays active.
79
- - A repeated non-empty `tools/list` continuation cursor rejects that update immediately, including cycles through empty pages; the previous tool set stays active and later updates can still succeed.
80
+ - The SDK owns discovery pagination and its page limit. A discovery failure preserves the previous tools; malformed cursor chains follow the SDK's behavior.
80
81
  - An update that conflicts with an already-registered tool name is rejected entirely — you never get a partial tool set from that server.
81
82
 
82
83
  ### Calling tools and reading results
@@ -115,19 +116,22 @@ This section explains the design decisions behind the bridge and points at the c
115
116
  |---|---|
116
117
  | [`src/index.ts`](src/index.ts) | Plugin entry: `Config` schema, `serverName` reservation, activation await |
117
118
  | [`src/connection.ts`](src/connection.ts) | Connection supervisor: client generations, reconnect policy, attempt budget, disposal |
119
+ | [`src/server-context.ts`](src/server-context.ts) | Resource-provider registration and literal server instructions |
118
120
  | [`src/tools.ts`](src/tools.ts) | Tool bridge: discovery, naming, registration swap, execution, image projection |
119
121
  | [`src/transport.ts`](src/transport.ts) | Transport factory: stdio spawn with scrubbed env, Streamable HTTP |
120
122
  | — | No runtime invariant companion is published; MCP generations contribute through the tool registry, but the bridge exposes no independent server-to-tool snapshot after an asynchronous resync. |
121
123
 
124
+ The exported `createMcpToolDefinition(ctx, options)` adapts an upstream tool schema and raw-result callback to the same canonical values, errors, and durable image projection. Each callback receives the exact `ToolExecution`, including its Agent and cancellation signal; SDK spec-type validation checks its result before projection. Callers own registration, cancellation deadlines, and provider teardown. The native Cua Driver provider uses this adapter without opening an MCP transport.
125
+
122
126
  ### Lifecycle and sync
123
127
 
124
- `apply` resolves the reconnect policy, reserves the `serverName` inside the current registration scope, starts the supervisor, and awaits the initial connection plus discovery. Independent Agent scopes may reuse the same namespace because their tools and transports are isolated; a duplicate inside one scope fails at load. The supervisor serializes every sync — initial, notification, and reconnect — through one queue so two syncs can never interleave their dispose-previous/register-next swap. Disposal cancels pending reconnects, closes the live client, waits for the in-flight attempt and queued syncs to quiesce, and unregisters the current generation.
128
+ `apply` resolves the reconnect policy, reserves the `serverName` inside the current registration scope, starts the supervisor, and awaits the initial connection plus discovery. Independent Agent scopes may reuse the same namespace because their tools and transports are isolated; a duplicate inside one scope fails at load. The supervisor serializes every sync — initial, notification, and reconnect — through one queue so two syncs can never interleave their dispose-previous/register-next swap. Disposal cancels pending reconnects, closes the negotiating transport or attached client, waits for the in-flight attempt and queued syncs to quiesce, and unregisters the current generation.
125
129
 
126
- The supervisor listens for `notifications/tools/list_changed` and queues a re-sync; a fetch-phase failure keeps the previous generation registered, while a registration conflict rolls back the attempted generation. Each outage shares one attempt budget: after `maxAttempts` consecutive failures the tools are unregistered and reconnection stops, and a connection that stays up past `maxDelayMs` resets the budget.
130
+ The SDK receives tool-list changes through legacy notifications or a modern subscription. The supervisor queues each re-sync; a fetch failure keeps the previous generation registered, while a registration conflict rolls back the attempted generation. Each outage shares one attempt budget: after `maxAttempts` consecutive failures the tools are unregistered and reconnection stops, and a connection that stays up past `maxDelayMs` resets the budget.
127
131
 
128
132
  ### Tool execution internals
129
133
 
130
- A tool call sends an uncached `tools/call` request carrying the raw MCP name, the JSON arguments, the abort signal, and the configured timeout; the public name is never sent to the server and never parsed back. Canonical success is `{ content: JsonValue[], structuredContent? }`, preserving the complete MCP JSON blocks for programmatic and PTC mode callers. A supported advertised `outputSchema` validates `structuredContent`; unsupported schema vocabulary falls back to unconstrained `JsonValue`. An MCP `isError` result throws before any image persistence, so the registry produces a failed tool result. Image batches are decoded and validated as a whole before any member is saved; any refusal projects every image as diagnostic text.
134
+ A tool call uses the SDK with the raw name, complete tool definition, JSON arguments, abort signal, and configured timeout. The SDK owns protocol validation, advertised output-schema validation, and modern request headers. Canonical success is `{ content: JsonValue[], structuredContent? }`, preserving valid MCP JSON blocks for programmatic and PTC mode callers. An MCP `isError` result throws before image persistence. The bridge validates each image batch before saving it; a refusal projects every image as diagnostic text.
131
135
 
132
136
  ### Environment scrubbing (stdio)
133
137
 
@@ -157,11 +161,11 @@ Read these pages when the package-level contract is not enough. They move from t
157
161
 
158
162
  #### What the model sees
159
163
 
160
- After initial discovery succeeds, every advertised MCP tool appears as a native tool named `mcp__<serverName>__<rawName>` (or its deterministic normalized form) with the server-provided description and input schema. A successful re-sync — including the one after an automatic reconnect — replaces the generation; plugin disposal or an exhausted reconnect budget removes it.
164
+ After discovery succeeds, SDK-admitted MCP tools appear as native tools named `mcp__<serverName>__<rawName>` (or their deterministic normalized form), with the server description and input schema. A re-sync replaces the generation; disposal or an exhausted reconnect budget removes it. A server without the tools capability connects with an empty tool set.
161
165
 
162
166
  #### Token effect
163
167
 
164
- The tool descriptions and input schemas enter every request while the tools are registered; re-syncs replace rather than accumulate schemas, and the server-qualified name adds tokens to every tool definition and call.
168
+ The tool descriptions and input schemas enter every request while the tools are registered; re-syncs replace rather than accumulate schemas, and the server-qualified name adds tokens to every tool definition and call. A configured client also enables the [shared resource tools and server-name prompt](../mcp-resources/README.md#model-experience).
165
169
 
166
170
  #### KV Cache effect
167
171
 
@@ -181,6 +185,20 @@ Arguments, mapped text, and durable image references are retained until compacti
181
185
 
182
186
  Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
183
187
 
188
+ ### Server instructions
189
+
190
+ #### What the model sees
191
+
192
+ One server-labeled section contains the nonblank instructions returned by each successful connection. Absent or blank instructions add no prompt text. Braces remain literal. A replacement connection publishes its instructions only after discovery succeeds; disposal or exhausted recovery removes the section.
193
+
194
+ #### Token effect
195
+
196
+ Server instructions contribute text to model requests while their scoped section is active. Resource documents enter history only through explicit resource reads.
197
+
198
+ #### KV Cache effect
199
+
200
+ Unchanged instructions retain identical prompt text. Updated or removed instructions change the next assembled system message and its reusable prefix.
201
+
184
202
  ## Known Limitations and Deferred Work
185
203
 
186
204
  <a id="known-limitations-and-deferred-work"></a>
@@ -188,11 +206,11 @@ Append-only; newly visible content follows the reusable request prefix and does
188
206
 
189
207
  These limits describe what you cannot do with this plugin and when it needs operational attention. They are current package constraints, not a comparison with other MCP clients or a task backlog.
190
208
 
191
- - **Tools are the only bridged MCP capability** — Resources and Prompts have no harness consumer mechanism and are deferred.
192
- - **Startup and discovery timeouts are inherited from the MCP SDK** — the plugin exposes no connection or discovery timeout; each `initialize` and paginated `tools/list` request uses the SDK's 60-second request default, so an unresponsive server or cursor chain can delay both activation and teardown while the initial synchronization settles.
193
- - **Reconnect triggers on transport close** — a crashed stdio child fires it; Streamable HTTP failures surface per request through the SDK transport's own recovery, so an unreachable HTTP server is retried per call rather than respawned by the supervisor.
209
+ - **Resources are read on demand** — shipped profiles provide the [shared resource service](../mcp-resources/README.md); resource subscriptions and MCP prompt templates are unsupported.
210
+ - **Startup and discovery timeouts are inherited from the MCP SDK** — the plugin exposes no separate connection or discovery timeout. Negotiation and discovery use the SDK's 60-second request default; discovery also uses its page limit. Plugin unload closes the transport to interrupt pending startup requests before awaiting teardown.
211
+ - **Reconnect handles failed negotiation and transport close** — a failed initial probe or crashed stdio child uses the configured reconnect budget. Once HTTP is connected, request failures use the SDK transport's recovery rather than respawning the connection.
194
212
  - **Image is the only durable rich-result bridge** — PNG, JPEG, WebP, and GIF enter Native context after exact capability proof. Audio and embedded-resource payloads remain execution-local with explicit diagnostics, while resource links preserve only their name and URI as text.
195
- - **Unsupported MCP output schemas are not enforced** — `structuredContent` falls back to `JsonValue` when the advertised schema uses vocabulary outside the harness subset.
213
+ - **Invalid protocol results or output schemas fail through the SDK** — the bridge does not accept legacy `toolResult` substitutes or bypass advertised schema validation.
196
214
  - **Task-required MCP tools are rejected at call time** — a tool that requires the task-based execution extension throws instead of bridging; the extension is not implemented.
197
215
 
198
216
  <a id="dev-note"></a>
@@ -204,9 +222,9 @@ These limits describe what you cannot do with this plugin and when it needs oper
204
222
  This Dev Note is working context for maintainers: open design questions and directions that are not decided. It is explicitly non-authoritative — shipped behavior, limits, and accepted rationale live in the sections above, the package code, and the linked Agent Notes.
205
223
 
206
224
  - The public-name algorithm is a v1 contract pinned by tests; changing it after release would break session history and permission rules.
207
- - An explicit DSH-owned connection and discovery timeout is an open direction; the SDK's 60-second default bounds startup and teardown.
225
+ - An explicit DSH-owned connection and discovery timeout is an open direction; the SDK's 60-second default bounds startup requests.
208
226
  - Reconnect ownership for Streamable HTTP is open: per-request retry is SDK behavior, and the supervisor could also own the HTTP generation.
209
- - Bridging MCP Resources needs a harness-side injection decision (system prompt, on demand, or model-triggered); bridging Prompts needs a prompt-template concept the harness lacks.
227
+ - MCP prompt templates need a separate user-selection and invocation mechanism.
210
228
  - The pinned MCP SDK is still evolving; a breaking upstream change requires updating the bridge.
211
229
 
212
230
  </details>
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@ kind: "package-reference"
9
9
 
10
10
  ## 概述
11
11
 
12
- `dsh-mcp-client` 让模型把外部 MCP(Model Context Protocol)服务器的工具当作 harness 原生工具调用。每台服务器配置一条记录,其工具便会以稳定名称出现,例如 `mcp__github__create_issue`。可将它用于文件系统、GitHub、数据库、记忆或其他 MCP 工具服务器;默认不启用任何服务器。工具定义会为每次模型请求增加 token;缓慢或崩溃的服务器可能延迟启动,或让工具调用失败直至恢复。本包只桥接工具;MCP resources 与 prompts 不受支持。
12
+ `dsh-mcp-client` 让模型使用外部 MCP(Model Context Protocol)服务器的工具与资源。每台服务器配置一条记录;其工具使用 `mcp__github__create_issue` 这样的名称。默认不启用任何服务器。随附 profile 已提供[共享资源发现与读取](../mcp-resources/README.zh.md)。调用方作用域为空时,不添加 MCP 工具或提示词文本。服务器指令作为字面文本加入已记录的系统提示词;MCP 提示词模板不受支持。缓慢或崩溃的服务器可能延迟启动,或让调用失败直至恢复。
13
13
 
14
14
  ## 目录
15
15
 
@@ -25,7 +25,7 @@ kind: "package-reference"
25
25
  <a id="use-this-package"></a>
26
26
  ## 使用本包
27
27
 
28
- 当模型需要把外部 MCP 服务器的工具当作原生工具调用时,添加 `dsh-mcp-client`。每台服务器一条配置项就是全部设置:给服务器一个简短的唯一名称和一种传输方式,它的工具就会以 `mcp__<serverName>__<tool>` 形式出现。服务器作为本地程序运行时选择 stdio,作为服务运行时选择 Streamable HTTP。如果你已经用其他客户端连接过 MCP 工具服务器,同样的配置行在这里也能用。
28
+ 当模型需要像调用原生工具一样调用外部 MCP 服务器时,添加 `dsh-mcp-client`。为每台服务器指定唯一名称和传输方式。官方 SDK 优先选择可用的 2026-07-28 协议,并回退到支持的旧版协议。本地程序使用 stdio,远端服务使用 Streamable HTTP;stdio 协商会先启动临时探测进程,再启动实际服务进程。
29
29
 
30
30
  ### 最小配置
31
31
 
@@ -58,16 +58,17 @@ kind: "package-reference"
58
58
  | `serverName` | 必填 | 服务器工具名称的 namespace;`[A-Za-z0-9_-]{1,32}`,在一个注册作用域内唯一 |
59
59
  | `command` / `args` / `env` / `cwd` | — | stdio:可执行文件、参数、合并到清洗过的环境之上的额外环境变量、工作目录 |
60
60
  | `url` / `headers` | — | streamable-http:端点 URL 与额外请求标头 |
61
- | `toolCallTimeoutMs` | `60,000` | 每次 `tools/call` 调用的超时 |
61
+ | `toolCallTimeoutMs` | `60,000` | 每次 `tools/call` 或资源请求的超时 |
62
+ | `maxInstructionBytes` | `32,768` | 包括服务器归属信息在内的服务器指令 UTF-8 字节上限;超出时连接失败 |
62
63
  | `failOnStartupError` | `false` | 初始连接或工具同步失败时拒绝插件激活 |
63
64
  | `reconnect.enabled` | `true` | 连接丢失后自动重新连接 |
64
65
  | `reconnect.initialDelayMs` | `500` | 首次重连延迟;每次连续失败尝试翻倍 |
65
66
  | `reconnect.maxDelayMs` | `30,000` | 退避上限;同时是重置尝试预算所需的正常运行时长 |
66
67
  | `reconnect.maxAttempts` | `10` | 每次中断内连续失败尝试次数上限,超出后放弃 |
67
68
 
68
- 生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-mcp-client)是每个受支持字段及其 JSDoc 的穷尽式真源。
69
+ 生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-mcp-client)是每个受支持字段的穷尽式真源。
69
70
 
70
- 启动后,服务器的工具会以 `mcp__<serverName>__<tool>` 形式出现——试着用一条提示词调用其中一个。如果初始连接失败,harness 仍会启动,但该服务器的工具不会出现,并会记录一条错误;设置 `failOnStartupError: true` 可让启动失败改为中止 harness。
71
+ 启动后,服务器的工具会以 `mcp__<serverName>__<tool>` 形式出现——试着用一条提示词调用其中一个。如果初始连接失败,harness 仍会启动,但该服务器的工具不会出现,并会记录一条错误。设置 `failOnStartupError: true` 会拒绝插件激活;[app-boot 的启动策略](../../boot/app-boot/README.zh.md)仍允许可选 MCP 配置项失败,而不中止 harness。
71
72
 
72
73
  ### 工具命名与共存
73
74
 
@@ -76,7 +77,7 @@ kind: "package-reference"
76
77
  - 发布相同工具名称(例如 `search`)的两个服务器会在各自的 namespace 下共存。
77
78
  - 两条配置项使用相同的服务器名称时,后加载的一条会在加载时以明确错误失败。
78
79
  - 服务器在工具列表中两次列出同一工具时,其工具列表会被作为无效列表拒绝,上一组工具保持可用。
79
- - `tools/list` 返回重复的非空续传游标时会立即拒绝本次更新,包括经过空页的循环;上一组工具保持可用,后续更新仍可成功。
80
+ - SDK 负责发现分页及页数上限。发现失败会保留之前的工具;格式错误的游标链遵循 SDK 的处理行为。
80
81
  - 工具更新与已有工具名称冲突时,该更新会被整体拒绝——绝不会得到该服务器的部分工具集。
81
82
 
82
83
  ### 调用工具与读取结果
@@ -104,7 +105,7 @@ kind: "package-reference"
104
105
  ### 设计理念
105
106
 
106
107
  - **服务器限定身份。** 每个 MCP 工具都有稳定的身份 `(serverName, rawName)`。namespace 是本地配置,绝不采用远程 `serverInfo.name`——远程名称不可信、在部署间不唯一、且升级时可能变化,这些都不允许静默重命名面向模型的工具。
107
- - **命名是固定约定。** 公开名称是 `(serverName, rawName)` 的纯函数,并满足 DeepSeek 函数名称约定;有损规范化会追加 12 位十六进制 SHA-256 hash,使不同身份绝不会折叠。会话历史与权限规则因此能在 HMR 替换、重新同步和其他服务器变化后保持有效。
108
+ - **命名是固定约定。** 公开名称是 `(serverName, rawName)` 的纯函数,并满足 DeepSeek 函数名称约定;有损规范化会追加 12 位十六进制 SHA-256 hash,使不同身份绝不会折叠。会话历史与权限规则因此能在 HMR(热模块替换)、重新同步和其他服务器变化后保持有效。
108
109
  - **原始名称是唯一的协议名称。** `tools/call` 始终收到原始名称;公开名称绝不会发给服务器,也绝不会被解析来还原原始名称。
109
110
  - **要么完整世代,要么没有。** 同步会原子地交换世代:获取失败保留上一世代,注册冲突则回滚整个尝试中的世代。
110
111
  - **一个规范值,一个投影。** 执行器返回协议完整的规范 `McpResult`;另一个有序投影准备 Native 内容,`finalizeContent` 只在注册表的执行后结果未变时安装它,因此策略块与值替换保持权威。
@@ -114,20 +115,23 @@ kind: "package-reference"
114
115
  | 文件 | 职责 |
115
116
  |---|---|
116
117
  | [`src/index.ts`](src/index.ts) | 插件入口:`Config` schema、`serverName` 预留、激活等待 |
117
- | [`src/connection.ts`](src/connection.ts) | 连接监督器:客户端世代、重连策略、尝试预算、dispose |
118
+ | [`src/connection.ts`](src/connection.ts) | 连接监督器:客户端世代、重连策略、尝试预算、dispose(资源释放) |
119
+ | [`src/server-context.ts`](src/server-context.ts) | 资源提供方注册与字面服务器指令 |
118
120
  | [`src/tools.ts`](src/tools.ts) | 工具桥接:发现、命名、注册交换、执行、图片投影 |
119
121
  | [`src/transport.ts`](src/transport.ts) | 传输工厂:带清洗环境的 stdio spawn、Streamable HTTP |
120
- | — | 不发布运行时不变式伴生入口;世代只能通过工具注册表观察。 |
122
+ | — | 不发布运行时不变式伴生入口;MCP 世代会通过工具注册表发挥作用,但桥接在异步重新同步后不提供独立的服务器工具映射快照。 |
123
+
124
+ 导出的 `createMcpToolDefinition(ctx, options)` 将上游工具 schema 和原始结果回调适配到相同的规范值、错误和持久化图像投影。每次回调都收到原样的 `ToolExecution`,包括其 Agent 和取消信号;SDK 的规范类型校验会在投影前检查返回结果。调用方负责注册、取消截止时间和提供方卸载。原生 Cua Driver 提供方使用此适配函数,无需打开 MCP 传输。
121
125
 
122
126
  ### 生命周期与同步
123
127
 
124
- `apply` 解析重连策略、在当前注册作用域内预留 `serverName`、启动监督器,并等待初始连接加发现完成。独立 Agent 作用域可以复用相同 namespace,因为其工具与传输彼此隔离;同一作用域内重复会在加载时失败。监督器把所有同步——初始、通知与重连——串行到同一条队列,因此两次同步绝不会交错执行各自的先 dispose 后注册交换。dispose 会取消待执行的重连、关闭活动客户端、等待进行中的尝试与排队同步完全停稳,然后注销当前世代。
128
+ `apply` 解析重连策略、在当前注册作用域内预留 `serverName`、启动监督器,并等待初始连接加发现完成。独立 agent(智能体)作用域可以复用相同 namespace,因为其工具与传输彼此隔离;同一作用域内重复会在加载时失败。监督器把所有同步——初始、通知与重连——串行到同一条队列,因此两次同步绝不会交错执行各自的先 dispose 后注册交换。dispose 会取消待执行的重连、关闭协商中的传输或已绑定的客户端、等待进行中的尝试与排队同步完全停稳,然后注销当前世代。
125
129
 
126
- 监督器监听 `notifications/tools/list_changed` 并排队一次重新同步;获取阶段失败时保留上一世代注册,注册冲突则回滚本次尝试的世代。每次中断共享一个尝试预算:连续失败达到 `maxAttempts` 次后工具被注销、重连停止;连接存活超过 `maxDelayMs` 会重置预算。
130
+ SDK 通过旧版通知或现代协议订阅接收工具列表变化。监督器将每次重新同步排队;获取失败时保留之前的注册代,注册冲突则回滚本次尝试。每次故障共享一个尝试预算:连续失败达到 `maxAttempts` 后注销工具并停止重连;连接持续超过 `maxDelayMs` 则重置预算。
127
131
 
128
132
  ### 工具执行内部细节
129
133
 
130
- 工具调用会发送一次未缓存的 `tools/call` 请求,携带原始 MCP 名称、JSON 参数、中止信号与配置的超时;公开名称绝不会发给服务器,也绝不会被解析还原。规范成功值是 `{ content: JsonValue[], structuredContent? }`,为程序化调用方与 PTC mode 调用方保留完整的 MCP JSON 块。受支持且已声明的 `outputSchema` 会验证 `structuredContent`;不受支持的 schema 词汇回退为不受约束的 `JsonValue`。MCP 的 `isError` 结果会在任何图片持久化之前抛出,使注册表产生失败的工具结果。图片批次会先整体解码并校验,再保存任一成员;任何拒绝都会把每张图片投影为诊断文本。
134
+ 工具调用向 SDK 提供原始名称、完整工具定义、JSON 参数、取消信号及配置的超时。SDK 负责协议校验、已声明输出 schema 的校验和现代协议请求 header。成功结果规范值为 `{ content: JsonValue[], structuredContent? }`,为编程调用方及 PTC 模式保留有效的 MCP JSON 块。MCP `isError` 结果会在图片持久化前抛出。桥接器在保存前校验整批图片;拒绝时将每张图片投影为诊断文本。
131
135
 
132
136
  ### 环境清洗(stdio)
133
137
 
@@ -157,11 +161,11 @@ kind: "package-reference"
157
161
 
158
162
  #### 模型看到什么
159
163
 
160
- 初始发现成功后,每个已声明的 MCP 工具都会显示为名为 `mcp__<serverName>__<rawName>`(或其确定性规范化形式)的原生工具,并携带服务器提供的描述与输入 schema。成功的重新同步——包括自动重连后的同步——会替换整个世代;对插件执行 dispose(资源释放)或重连预算耗尽会移除该世代。
164
+ 发现成功后,SDK 接受的 MCP 工具以原生工具名称 `mcp__<serverName>__<rawName>`(或其确定性规范化形式)出现,携带服务器描述和输入 schema。重新同步会替换注册代;释放或重连预算耗尽会移除工具。未声明 tools 能力的服务器以空工具集连接。
161
165
 
162
166
  #### Token 影响
163
167
 
164
- 工具注册期间,工具描述与输入 schema 会进入每次请求;重新同步会替换而非累积 schema,服务器限定名称也会为每个工具定义和调用增加 token。
168
+ 工具注册期间,工具描述与输入 schema 会进入每次请求;重新同步会替换而非累积 schema,服务器限定名称也会为每个工具定义和调用增加 token。已配置客户端还会启用[共享资源工具与服务器名称提示词](../mcp-resources/README.zh.md#model-experience)。
165
169
 
166
170
  #### KV Cache 影响
167
171
 
@@ -171,7 +175,7 @@ kind: "package-reference"
171
175
 
172
176
  #### 模型看到什么
173
177
 
174
- 公开工具名称和 JSON 参数保留在 assistant 历史中。规范值始终为程序化调用方与 PTC mode 调用方保留完整的 MCP JSON 块与可选结构化内容;受支持的图片块在确切路由能力得到证明后,按原始顺序与文本一起投影。被拒绝的图片、音频、嵌入资源、资源链接与未知块继续以有界文本诊断可见;MCP `isError` 会在图片持久化之前拒绝调用。
178
+ 公开工具名称和 JSON 参数保留在 assistant 历史中。规范值始终为程序化调用方与 PTC 模式调用方保留完整的 MCP JSON 块与可选结构化内容;受支持的图片块在确切路由能力得到证明后,按原始顺序与文本一起投影。被拒绝的图片、音频、嵌入资源、资源链接与未知块继续以有界文本诊断可见;MCP `isError` 会在图片持久化之前拒绝调用。
175
179
 
176
180
  #### Token 影响
177
181
 
@@ -181,6 +185,20 @@ kind: "package-reference"
181
185
 
182
186
  仅追加;新可见内容位于可复用请求前缀之后,不会使现有 KV-cache 条目失效。
183
187
 
188
+ ### 服务器指令
189
+
190
+ #### 模型看到什么
191
+
192
+ 每个成功连接返回的非空白指令保存在一个带服务器名称的段落中。未返回指令或指令仅含空白时,不向提示词添加文本。花括号保持字面值。替代连接仅在发现成功后发布其指令;释放或耗尽恢复预算时移除该段落。
193
+
194
+ #### Token 影响
195
+
196
+ 作用域段落生效期间,服务器指令为模型请求贡献文本。资源文档仅通过显式资源读取进入历史。
197
+
198
+ #### KV Cache 影响
199
+
200
+ 未变化的指令保留相同提示词文本。更新或移除指令会改变下一次组装的系统消息及其可复用前缀。
201
+
184
202
  ## 已知限制与延期工作
185
203
 
186
204
  <a id="known-limitations-and-deferred-work"></a>
@@ -188,11 +206,11 @@ kind: "package-reference"
188
206
 
189
207
  这些限制说明你无法用本插件做什么、以及何时需要运维注意。它们是当前包约束,不是与其他 MCP 客户端的对比,也不是任务积压。
190
208
 
191
- - **只桥接 MCP 的工具能力**——Resources 与 Prompts 没有 harness 消费机制,暂缓实现。
192
- - **启动与发现超时继承自 MCP SDK**——插件不暴露连接或发现超时;每次 `initialize` 与分页 `tools/list` 请求都使用 SDK 默认的 60 秒请求超时,因此无响应的服务器或 cursor chain 在初始同步完成期间可能同时延迟激活与 teardown。
193
- - **重连在传输关闭时触发**——崩溃的 stdio 子进程会触发重连;Streamable HTTP 失败按请求经 SDK 传输自身的恢复机制暴露,因此不可达的 HTTP 服务器会按调用重试,而非由 supervisor 重新 spawn。
209
+ - **资源按需读取**——随附 profile 提供[共享资源服务](../mcp-resources/README.zh.md);资源订阅与 MCP 提示词模板不受支持。
210
+ - **启动与发现超时继承自 MCP SDK**——插件不暴露单独的连接或发现超时。协商与发现使用 SDK 默认的 60 秒请求超时;发现也使用 SDK 的页数上限。插件卸载先关闭传输以中断待处理的启动请求,再等待清理。
211
+ - **重连处理协商失败与传输关闭**——初始探测失败或 stdio 子进程崩溃都会使用配置的重连预算。HTTP 建立连接后,请求失败使用 SDK 传输的恢复机制,而非重新创建连接。
194
212
  - **图片是唯一的持久丰富结果桥接**——PNG、JPEG、WebP 与 GIF 在确切能力得到证明后进入 Native 上下文。音频与嵌入资源载荷仍只存在于执行局部并带明确诊断,资源链接只以文本保留名称与 URI。
195
- - **不强制执行不受支持的 MCP 输出 schema**——已声明 schema 使用 harness 子集之外的词汇时,`structuredContent` 回退为 `JsonValue`。
213
+ - **无效的协议结果或输出 schema 由 SDK 拒绝**——桥接器不接受旧式 `toolResult` 替代结果,也不绕过已声明的 schema 校验。
196
214
  - **要求基于任务的 MCP 工具在调用时被拒绝**——要求使用基于任务的执行(task-based execution)扩展的工具会抛出异常而非被桥接;该扩展未实现。
197
215
 
198
216
  <a id="dev-note"></a>
@@ -204,9 +222,9 @@ kind: "package-reference"
204
222
  本开发备注是维护者的工作上下文:开放设计问题与尚未决定的探索方向。它明确不具权威性——已交付行为、限制与既定理由以上文、包代码与所链接的 Agent Note 为准。
205
223
 
206
224
  - 公开名称算法是由测试固定的 v1 约定;发布后更改会破坏会话历史与权限规则。
207
- - 由 DSH 显式拥有的连接与发现超时是开放的探索方向;SDK 的 60 秒默认值约束着启动与 teardown。
225
+ - 由 DSH 显式拥有的连接与发现超时是开放的探索方向;SDK 的 60 秒默认值约束着启动请求。
208
226
  - Streamable HTTP 的重连归属仍未决定:按请求重试是 SDK 行为,supervisor 也可以拥有 HTTP 世代。
209
- - 桥接 MCP Resources 需要 harness 侧的注入决策(系统提示词、按需或模型触发);桥接 Prompts 需要 harness 缺少的提示词模板概念。
227
+ - MCP 提示词模板需要独立的用户选择和模板调用机制。
210
228
  - 固定的 MCP SDK 仍在演化;上游破坏性变更需要更新桥接。
211
229
 
212
230
  </details>
package/lib/index.js CHANGED
@@ -1,14 +1,12 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
2
  import { scopeOf } from "@deepseek-ai/dsh-scope";
3
3
  import { MAX_TIMER_DELAY_MS } from "@deepseek-ai/dsh-timeout";
4
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
5
- import { ListToolsResultSchema, ToolListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
6
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
7
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
4
+ import { Client, StreamableHTTPClientTransport, specTypeSchemas } from "@modelcontextprotocol/client";
5
+ import { assertNever } from "@deepseek-ai/dsh-util-values";
6
+ import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
8
7
  import { scrubbedParentEnv } from "@deepseek-ai/dsh-subprocess";
9
8
  import { createHash } from "node:crypto";
10
9
  import { isDeepStrictEqual } from "node:util";
11
- import { z as z$1 } from "zod";
12
10
  import { isImageAdmissionError } from "@deepseek-ai/dsh-attachment";
13
11
  import { assertSupportedJsonSchema } from "@deepseek-ai/dsh-tools";
14
12
  //#region lib/types/transport.js
@@ -72,8 +70,6 @@ const MAX_PUBLIC_NAME_LENGTH = 64;
72
70
  const INVALID_NAME_CHARS = /[^A-Za-z0-9_-]/g;
73
71
  /** Hex chars of the SHA-256 identity hash appended on lossy normalization. */
74
72
  const HASH_LENGTH = 12;
75
- /** Raw result record: the bridge owns JSON-value validation after transport. */
76
- const RawCallToolResultSchema = z$1.record(z$1.string(), z$1.unknown());
77
73
  /** Raster formats supported by the durable attachment vocabulary. */
78
74
  const IMAGE_MEDIA_TYPES = [
79
75
  "image/png",
@@ -83,26 +79,6 @@ const IMAGE_MEDIA_TYPES = [
83
79
  ];
84
80
  /** Canonical RFC 4648 base64, excluding whitespace and URL-safe aliases. */
85
81
  const CANONICAL_BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
86
- /** List without mutating the SDK's per-page output-validator cache. */
87
- function listToolsUncached(client, cursor) {
88
- return client.request({
89
- method: "tools/list",
90
- ...cursor === void 0 ? {} : { params: { cursor } }
91
- }, ListToolsResultSchema);
92
- }
93
- /** Call without the SDK pre-validating an output schema the bridge may not support. */
94
- function callToolUncached(client, rawName, args, exec, opts) {
95
- return client.request({
96
- method: "tools/call",
97
- params: {
98
- name: rawName,
99
- arguments: args
100
- }
101
- }, RawCallToolResultSchema, {
102
- signal: exec.signal,
103
- timeout: opts.toolCallTimeoutMs
104
- });
105
- }
106
82
  /**
107
83
  * Derive the model-facing public name for one MCP tool.
108
84
  *
@@ -129,9 +105,9 @@ function publicToolName(serverName, rawName) {
129
105
  *
130
106
  * Two phases keep the swap safe:
131
107
  *
132
- * 1. Fetch: drain uncached `tools/list` pagination and build the full next
108
+ * 1. Fetch: let the SDK aggregate `tools/list` and build the full next
133
109
  * generation of `ToolDefinition`s under public names. Any failure here
134
- * (network error, duplicate raw name, repeated continuation cursor) rejects
110
+ * (network error or duplicate raw name) rejects
135
111
  * and leaves the previous generation registered untouched.
136
112
  * 2. Swap: dispose the previous generation, register the new one. A registry
137
113
  * conflict here can only mean a foreign registration squats on this
@@ -150,21 +126,27 @@ function publicToolName(serverName, rawName) {
150
126
  */
151
127
  async function syncTools(client, ctx, opts, previous) {
152
128
  const definitions = /* @__PURE__ */ new Map();
153
- const seenCursors = /* @__PURE__ */ new Set();
154
- let cursor;
155
- do {
156
- const response = await listToolsUncached(client, cursor);
157
- for (const tool of response.tools) {
158
- const publicName = publicToolName(opts.serverName, tool.name);
159
- if (definitions.has(publicName)) throw new Error(`mcp-client(${opts.serverName}): server listed tool "${tool.name}" more than once — invalid tool list`);
160
- definitions.set(publicName, createDefinition(client, ctx, publicName, tool.name, tool.description ?? "", tool.inputSchema, supportedOutputSchema(tool.outputSchema), tool.execution?.taskSupport === "required", opts));
161
- }
162
- cursor = response.nextCursor;
163
- if (cursor) {
164
- if (seenCursors.has(cursor)) throw new Error(`mcp-client(${opts.serverName}): server repeated a tools/list continuation cursor — invalid tool list`);
165
- seenCursors.add(cursor);
166
- }
167
- } while (cursor);
129
+ const response = client.getServerCapabilities()?.tools === void 0 ? { tools: [] } : await client.listTools(void 0, { cacheMode: "refresh" });
130
+ for (const tool of response.tools) {
131
+ const publicName = publicToolName(opts.serverName, tool.name);
132
+ if (definitions.has(publicName)) throw new Error(`mcp-client(${opts.serverName}): server listed tool "${tool.name}" more than once — invalid tool list`);
133
+ definitions.set(publicName, createMcpToolDefinition(ctx, {
134
+ name: publicName,
135
+ rawName: tool.name,
136
+ description: tool.description ?? "",
137
+ inputSchema: tool.inputSchema,
138
+ outputSchema: tool.outputSchema,
139
+ taskRequired: tool.execution?.taskSupport === "required",
140
+ call: (args, execution) => client.callTool({
141
+ name: tool.name,
142
+ arguments: args
143
+ }, {
144
+ signal: execution.signal,
145
+ timeout: opts.toolCallTimeoutMs,
146
+ toolDefinition: tool
147
+ })
148
+ }));
149
+ }
168
150
  for (const dispose of previous.values()) dispose();
169
151
  const disposers = /* @__PURE__ */ new Map();
170
152
  try {
@@ -188,26 +170,21 @@ function supportedOutputSchema(candidate) {
188
170
  }
189
171
  }
190
172
  /**
191
- * Build one generation-local tool definition and its execution-local rich projections.
192
- * @param client - connected MCP client used for calls.
173
+ * Adapt an upstream MCP tool to canonical values and durable image content.
174
+ * Registration, provider lifetime, deadlines, and transport belong to the caller.
193
175
  * @param ctx - plugin context carrying optional attachment and model services.
194
- * @param publicName - registry-qualified public tool name.
195
- * @param rawName - MCP wire tool name.
196
- * @param description - model-facing tool description.
197
- * @param parameters - MCP input schema.
198
- * @param structuredSchema - supported structured-output schema, when advertised.
199
- * @param taskRequired - whether this MCP tool requires unsupported task execution.
200
- * @param opts - bridge timeout and namespace options.
201
- * @returns a complete ToolRuntime definition.
176
+ * @param options - upstream tool fields and its raw-result callback.
177
+ * @returns the unregistered ToolRuntime definition.
202
178
  */
203
- function createDefinition(client, ctx, publicName, rawName, description, parameters, structuredSchema, taskRequired, opts) {
179
+ function createMcpToolDefinition(ctx, options) {
180
+ const { name, rawName, description, inputSchema } = options;
204
181
  const projections = /* @__PURE__ */ new WeakMap();
205
182
  return {
206
- name: publicName,
183
+ name,
207
184
  description,
208
- parameters,
209
- output: createOutput(rawName, structuredSchema),
210
- execute: createExecutor(client, ctx, rawName, taskRequired, opts, projections),
185
+ parameters: inputSchema,
186
+ output: createOutput(rawName, supportedOutputSchema(options.outputSchema)),
187
+ execute: createExecutor(ctx, options, projections),
211
188
  finalizeContent(exec, result) {
212
189
  const projection = projections.get(exec);
213
190
  if (projection === void 0) return void 0;
@@ -243,31 +220,17 @@ function createOutput(rawName, structuredSchema) {
243
220
  };
244
221
  }
245
222
  /**
246
- * Create an execute function for one MCP tool. The executor closes over the
247
- * raw MCP tool name and sends an uncached `tools/call` request with it (never
248
- * the public name), with abort signal and timeout, then maps the result to
249
- * harness ContentBlocks. Owning the raw request prevents the SDK's internal
250
- * per-page schema cache from pre-validating a different contract.
251
- *
252
- * When the MCP server returns `isError: true`, the executor throws so that
253
- * the ToolRuntime's catch path produces an `isError` result for the model.
223
+ * Invoke the caller-owned raw-result callback and prepare canonical content.
224
+ * MCP isError results reject before image storage so ToolRuntime records failure.
254
225
  */
255
- function createExecutor(client, ctx, rawName, taskRequired, opts, projections) {
226
+ function createExecutor(ctx, options, projections) {
227
+ const { rawName, taskRequired } = options;
256
228
  return async (args, exec) => {
257
229
  if (taskRequired) throw new Error(`Tool "${rawName}" requires task-based execution, which this bridge does not support`);
258
- const result = await callToolUncached(client, rawName, typeof args === "object" && args !== null ? args : {}, exec, opts);
259
- if (!Array.isArray(result.content)) {
260
- const rendered = "toolResult" in result ? JSON.stringify(result.toolResult) : "(no output)";
261
- const text = typeof rendered === "string" ? rendered : "(no output)";
262
- if (result.isError === true) throw new Error(text);
263
- return {
264
- content: [{
265
- type: "text",
266
- text
267
- }],
268
- ...result.structuredContent !== void 0 ? { structuredContent: result.structuredContent } : {}
269
- };
270
- }
230
+ const argsObj = typeof args === "object" && args !== null ? args : {};
231
+ const parsed = specTypeSchemas.CallToolResult["~standard"].validate(await options.call(argsObj, exec));
232
+ if (parsed.issues !== void 0) throw new Error(`Tool "${rawName}" returned an invalid MCP result: ${parsed.issues.map((issue) => issue.message).join("; ")}`);
233
+ const result = parsed.value;
271
234
  const content = result.content;
272
235
  const text = extractText(content, rawName);
273
236
  if (result.isError === true) throw new Error(text);
@@ -302,10 +265,10 @@ function isRecord(value) {
302
265
  function isImageMediaType(value) {
303
266
  return IMAGE_MEDIA_TYPES.includes(value);
304
267
  }
305
- /** Decode one untrusted MCP image block without accepting base64 aliases. */
268
+ /** Decode one projected image without accepting base64 aliases. */
306
269
  function decodeImage(block) {
307
- if (block.mimeType === void 0 || !isImageMediaType(block.mimeType)) throw new Error("the declared media type is not PNG, JPEG, WebP, or GIF");
308
- if (block.data === void 0 || !CANONICAL_BASE64.test(block.data)) throw new Error("the image data is not canonical base64");
270
+ if (!isImageMediaType(block.mimeType)) throw new Error("the declared media type is not PNG, JPEG, WebP, or GIF");
271
+ if (!CANONICAL_BASE64.test(block.data)) throw new Error("the image data is not canonical base64");
309
272
  const data = Buffer.from(block.data, "base64");
310
273
  if (data.toString("base64") !== block.data) throw new Error("the image data is not canonical base64");
311
274
  return {
@@ -393,8 +356,7 @@ async function prepareImageProjection(ctx, exec, content, toolName) {
393
356
  * - text blocks: join with '\n'
394
357
  * - image/audio/resource blocks: replaced with a placeholder
395
358
  *
396
- * Defensive: fields that the MCP spec declares required (mimeType, text) are
397
- * guarded with fallbacks because this is a network trust boundary.
359
+ * Policy-owned canonical-value replacements may omit fields required on the MCP wire.
398
360
  */
399
361
  function extractText(mcpContent, toolName) {
400
362
  return projectContent(mcpContent, toolName).map((block) => block.text).join("\n");
@@ -475,6 +437,8 @@ const RECONNECT_DEFAULTS = Object.freeze({
475
437
  maxDelayMs: 3e4,
476
438
  maxAttempts: 10
477
439
  });
440
+ /** Default UTF-8 byte limit for attributed server instructions. */
441
+ const DEFAULT_MAX_INSTRUCTION_BYTES = 32768;
478
442
  const GENERATION_CLOSE_TIMEOUT_MS = 5e3;
479
443
  /**
480
444
  * The one explicit resolve step from raw reconnect config to the policy the
@@ -516,6 +480,7 @@ function resolveReconnectPolicy(config, path) {
516
480
  */
517
481
  function startConnection(ctx, config, policy) {
518
482
  const label = `mcp-client(${config.serverName})`;
483
+ const incompleteDisposalMessage = `${label}: transport closure could not be confirmed during disposal — server shutdown may be incomplete`;
519
484
  const opts = {
520
485
  registrationFailure: "contain",
521
486
  serverName: config.serverName,
@@ -526,10 +491,12 @@ function startConnection(ctx, config, policy) {
526
491
  registrationFailure: "throw"
527
492
  } : opts;
528
493
  let disposed = false;
494
+ const maxInstructionBytes = config.maxInstructionBytes ?? 32768;
495
+ let serverInstructions = "";
529
496
  /** Current generation: the connecting or connected client; undefined during backoff waits and after final failure. */
530
497
  let client;
531
- /** Close signal paired with {@link client}; captured by dispose before current ownership is cleared. */
532
- let clientClosed;
498
+ /** Transport-aware close operation paired with {@link client}. */
499
+ let closeClient;
533
500
  /** Live tool registrations owned by this server; only {@link enqueueSync} and dispose swap it. */
534
501
  let disposers = /* @__PURE__ */ new Map();
535
502
  let reconnectTimer;
@@ -560,9 +527,20 @@ function startConnection(ctx, config, policy) {
560
527
  function generationDown(generation) {
561
528
  if (!isCurrent(generation)) return;
562
529
  client = void 0;
563
- clientClosed = void 0;
530
+ closeClient = void 0;
564
531
  scheduleReconnect();
565
532
  }
533
+ /** Decide retry ownership after a failed connection's close barrier settles. */
534
+ function settleFailedGeneration(generation, quiesced) {
535
+ if (!isCurrent(generation)) return;
536
+ if (!quiesced) {
537
+ client = void 0;
538
+ closeClient = void 0;
539
+ ctx.logger.error(`${label}: failed generation could not confirm transport closure — reconnect stopped to avoid overlapping server processes; reload the plugin or restart the Host to retry`);
540
+ return;
541
+ }
542
+ generationDown(generation);
543
+ }
566
544
  /** Wait for the transport-owned close signal without letting a broken transport wedge teardown forever. */
567
545
  function waitForClose(closed) {
568
546
  return new Promise((resolve) => {
@@ -590,6 +568,7 @@ function startConnection(ctx, config, policy) {
590
568
  syncChain = syncChain.then(() => {
591
569
  for (const dispose of disposers.values()) dispose();
592
570
  disposers = /* @__PURE__ */ new Map();
571
+ serverInstructions = "";
593
572
  });
594
573
  ctx.logger.error(`${label}: giving up after ${policy.maxAttempts} consecutive failed reconnect attempts — tools unregistered; reload the plugin or restart the Host to reconnect`);
595
574
  return;
@@ -617,19 +596,40 @@ function startConnection(ctx, config, policy) {
617
596
  const generation = new Client({
618
597
  name: "dsh-mcp-client",
619
598
  version: "0.0.1"
620
- }, { capabilities: {} });
599
+ }, {
600
+ capabilities: {},
601
+ versionNegotiation: { mode: "auto" },
602
+ listChanged: { tools: {
603
+ autoRefresh: false,
604
+ debounceMs: 0,
605
+ onChanged: () => {
606
+ refreshTools();
607
+ }
608
+ } }
609
+ });
621
610
  const closed = Promise.withResolvers();
622
611
  let attemptSettled = false;
623
612
  let closeObserved = false;
613
+ let transport;
624
614
  const hasClosed = () => closeObserved;
625
615
  client = generation;
626
- clientClosed = closed.promise;
616
+ closeClient = closeGeneration;
627
617
  generation.onclose = () => {
628
618
  closeObserved = true;
629
619
  closed.resolve();
630
620
  if (attemptSettled) generationDown(generation);
631
621
  };
632
- generation.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
622
+ /** Unattached probes close through their transport; attached clients must also report transport closure. */
623
+ async function closeGeneration() {
624
+ const attached = generation.transport !== void 0;
625
+ try {
626
+ await (attached ? generation.close() : transport?.close());
627
+ } catch (_error) {
628
+ if (!attached) return hasClosed();
629
+ }
630
+ return !attached || hasClosed() || await waitForClose(closed.promise);
631
+ }
632
+ async function refreshTools() {
633
633
  if (!isCurrent(generation)) return;
634
634
  ctx.logger.info(`${label}: tool list changed, re-syncing`);
635
635
  try {
@@ -637,31 +637,30 @@ function startConnection(ctx, config, policy) {
637
637
  } catch (error) {
638
638
  if (!disposed) ctx.logger.error(`${label}: tool re-sync failed: ${String(error)}`);
639
639
  }
640
- });
640
+ }
641
+ let instructions;
641
642
  try {
642
- await generation.connect(createTransport(config));
643
+ transport = createTransport(config);
644
+ await generation.connect(transport);
643
645
  if (hasClosed()) {
644
646
  attemptSettled = true;
645
647
  generationDown(generation);
646
648
  return;
647
649
  }
650
+ if (!isCurrent(generation)) {
651
+ if (!await closeGeneration()) ctx.logger.error(incompleteDisposalMessage);
652
+ return;
653
+ }
654
+ const serverText = generation.getInstructions()?.trimEnd() ?? "";
655
+ instructions = serverText ? `### MCP server: ${config.serverName}\n\n${serverText}` : "";
656
+ if (Buffer.byteLength(instructions) > maxInstructionBytes) throw new Error(`${label}: server instructions exceed maxInstructionBytes (${maxInstructionBytes})`);
648
657
  await enqueueSync(generation, startup ? startupOpts : opts);
649
658
  } catch (error) {
650
659
  if (firstAttemptError === void 0) firstAttemptError = error;
651
660
  if (isCurrent(generation)) ctx.logger.warn(`${label}: connection attempt failed: ${String(error)}`);
652
- try {
653
- await generation.close();
654
- } catch {}
655
- const quiesced = hasClosed() || await waitForClose(closed.promise);
661
+ const quiesced = await closeGeneration();
656
662
  attemptSettled = true;
657
- if (!isCurrent(generation)) return;
658
- if (!quiesced) {
659
- client = void 0;
660
- clientClosed = void 0;
661
- ctx.logger.error(`${label}: failed generation did not close within ${GENERATION_CLOSE_TIMEOUT_MS}ms — reconnect stopped to avoid overlapping server processes; reload the plugin or restart the Host to retry`);
662
- return;
663
- }
664
- generationDown(generation);
663
+ settleFailedGeneration(generation, quiesced);
665
664
  return;
666
665
  }
667
666
  attemptSettled = true;
@@ -670,6 +669,7 @@ function startConnection(ctx, config, policy) {
670
669
  return;
671
670
  }
672
671
  if (!isCurrent(generation)) return;
672
+ serverInstructions = instructions;
673
673
  connectedAt = Date.now();
674
674
  if (failedAttempts > 0) ctx.logger.info(`${label}: reconnected and re-synced tools (attempt ${failedAttempts}/${policy.maxAttempts})`);
675
675
  }
@@ -681,22 +681,33 @@ function startConnection(ctx, config, policy) {
681
681
  /* v8 ignore next -- defensive: firstAttemptError is always set when connect/sync fails */
682
682
  return { error: firstAttemptError ?? /* @__PURE__ */ new Error(`${label}: initial connection failed`) };
683
683
  }),
684
+ instructions: () => serverInstructions,
685
+ resources: { async request(request, exec) {
686
+ const generation = client;
687
+ if (!generation || connectedAt === void 0) throw new Error(`${label}: server is disconnected`);
688
+ const options = {
689
+ signal: exec.signal,
690
+ timeout: config.toolCallTimeoutMs
691
+ };
692
+ switch (request.method) {
693
+ case "resources/list": return await generation.listResources(request.cursor === void 0 ? void 0 : { cursor: request.cursor }, options);
694
+ case "resources/templates/list": return await generation.listResourceTemplates(request.cursor === void 0 ? void 0 : { cursor: request.cursor }, options);
695
+ case "resources/read": return await generation.readResource({ uri: request.uri }, options);
696
+ /* v8 ignore next 2 -- resource requests are the closed, typed tool operation union */
697
+ default: return assertNever(request);
698
+ }
699
+ } },
684
700
  async dispose() {
685
701
  disposed = true;
702
+ serverInstructions = "";
686
703
  if (reconnectTimer !== void 0) {
687
704
  clearTimeout(reconnectTimer);
688
705
  reconnectTimer = void 0;
689
706
  }
690
- const current = client;
691
- const currentClosed = clientClosed;
707
+ const close = closeClient;
692
708
  client = void 0;
693
- clientClosed = void 0;
694
- if (current !== void 0) {
695
- try {
696
- await current.close();
697
- } catch {}
698
- if (currentClosed !== void 0 && !await waitForClose(currentClosed)) ctx.logger.error(`${label}: generation did not close within ${GENERATION_CLOSE_TIMEOUT_MS}ms during disposal — server shutdown may be incomplete`);
699
- }
709
+ closeClient = void 0;
710
+ if (close !== void 0 && !await close()) ctx.logger.error(incompleteDisposalMessage);
700
711
  await settling;
701
712
  await syncChain;
702
713
  for (const dispose of disposers.values()) dispose();
@@ -705,6 +716,32 @@ function startConnection(ctx, config, policy) {
705
716
  };
706
717
  }
707
718
  //#endregion
719
+ //#region lib/types/server-context.js
720
+ /**
721
+ * Publish connection-owned MCP resources and literal server instructions.
722
+ *
723
+ * @module @deepseek-ai/dsh-mcp-client
724
+ */
725
+ /**
726
+ * Contribute server context to the services enabled by this composition.
727
+ * @param ctx - server plugin's registration scope and effect owner.
728
+ * @param server - configured server identity.
729
+ * @param connection - live resource operations and successful instruction snapshot.
730
+ */
731
+ function registerServerContext(ctx, server, connection) {
732
+ ctx.inject(["mcpResources"], (inner) => {
733
+ inner.mcpResources.register(server, connection.resources);
734
+ });
735
+ ctx.inject(["systemPrompt"], (inner) => {
736
+ inner.systemPrompt.section({
737
+ name: `mcp:${server}`,
738
+ order: inner.systemPrompt.getSectionOrder("MCP_SERVERS"),
739
+ interpolate: false,
740
+ text: () => connection.instructions()
741
+ });
742
+ });
743
+ }
744
+ //#endregion
708
745
  //#region lib/types/index.js
709
746
  /**
710
747
  * MCP client bridge plugin: connects to an external MCP server and registers
@@ -724,7 +761,7 @@ function startConnection(ctx, config, policy) {
724
761
  const name = "mcp-client";
725
762
  /** Services required by this plugin. */
726
763
  const inject = ["tools"];
727
- /** Default timeout for individual MCP tool calls (ms). */
764
+ /** Default timeout for individual MCP tool calls and resource requests (ms). */
728
765
  const DEFAULT_TOOL_CALL_TIMEOUT_MS = 6e4;
729
766
  /** Valid `serverName`, kept below the public tool-name budget. */
730
767
  const SERVER_NAME_PATTERN = /^[A-Za-z0-9_-]{1,32}$/;
@@ -749,6 +786,7 @@ const Config = z.union([z.object({
749
786
  cwd: z.string().default(""),
750
787
  toolCallTimeoutMs: z.number().default(DEFAULT_TOOL_CALL_TIMEOUT_MS),
751
788
  failOnStartupError: z.boolean().default(false),
789
+ maxInstructionBytes: z.number().step(1).min(1).default(DEFAULT_MAX_INSTRUCTION_BYTES),
752
790
  reconnect: Reconnect
753
791
  }), z.object({
754
792
  transport: z.const("streamable-http"),
@@ -757,6 +795,7 @@ const Config = z.union([z.object({
757
795
  headers: z.dict(String).default({}),
758
796
  toolCallTimeoutMs: z.number().default(DEFAULT_TOOL_CALL_TIMEOUT_MS),
759
797
  failOnStartupError: z.boolean().default(false),
798
+ maxInstructionBytes: z.number().step(1).min(1).default(DEFAULT_MAX_INSTRUCTION_BYTES),
760
799
  reconnect: Reconnect
761
800
  })]);
762
801
  /**
@@ -781,11 +820,16 @@ async function apply(ctx, config) {
781
820
  return () => void names.delete(config.serverName);
782
821
  }, "mcp-client.serverName");
783
822
  const connection = startConnection(ctx, config, reconnect);
784
- ctx.effect(() => {
785
- return () => connection.dispose();
786
- }, "mcp-client.connection");
823
+ registerServerContext(ctx, config.serverName, connection);
824
+ let stopping;
825
+ const dispose = () => stopping ??= connection.dispose();
826
+ ctx.on("internal/plugin", (fiber) => {
827
+ if (fiber !== ctx.fiber || fiber.uid !== null) return;
828
+ return dispose();
829
+ }, { global: true });
830
+ ctx.effect(() => dispose, "mcp-client.connection");
787
831
  const outcome = await connection.ready;
788
832
  if (outcome.error !== void 0 && config.failOnStartupError) throw new Error(`mcp-client(${config.serverName}): initial connection or tool synchronization failed`, { cause: outcome.error });
789
833
  }
790
834
  //#endregion
791
- export { Config, apply, inject, name };
835
+ export { Config, apply, createMcpToolDefinition, inject, name };
@@ -15,6 +15,7 @@
15
15
  * @module
16
16
  */
17
17
  import type { Context } from '@deepseek-ai/cordis';
18
+ import type { ServerContext } from './server-context.ts';
18
19
  import type { Config } from './index.ts';
19
20
  /** Automatic reconnect policy for one MCP server connection. */
20
21
  export interface ReconnectConfig {
@@ -29,6 +30,8 @@ export interface ReconnectConfig {
29
30
  }
30
31
  /** Defaults shared by the Config schema and {@link resolveReconnectPolicy}. */
31
32
  export declare const RECONNECT_DEFAULTS: Required<ReconnectConfig>;
33
+ /** Default UTF-8 byte limit for attributed server instructions. */
34
+ export declare const DEFAULT_MAX_INSTRUCTION_BYTES = 32768;
32
35
  /** Fully resolved reconnect policy captured at plugin load. */
33
36
  export type ResolvedReconnectPolicy = Readonly<Required<ReconnectConfig>>;
34
37
  /**
@@ -48,7 +51,7 @@ export interface ConnectionOutcome {
48
51
  error?: unknown;
49
52
  }
50
53
  /** Handle for one plugin instance's supervised connection. */
51
- export interface ConnectionHandle {
54
+ export interface ConnectionHandle extends ServerContext {
52
55
  /**
53
56
  * Settles when the first connection attempt completes (success or failure).
54
57
  * The supervisor enters its reconnect loop regardless; the caller decides
@@ -56,9 +59,9 @@ export interface ConnectionHandle {
56
59
  */
57
60
  ready: Promise<ConnectionOutcome>;
58
61
  /**
59
- * Stop reconnection, close the live client, wait for the in-flight attempt
60
- * and queued tool syncs to quiesce, then unregister every tool this server
61
- * still owns.
62
+ * Stop reconnection, close the negotiating transport or live client, wait
63
+ * for the in-flight attempt and queued tool syncs to quiesce, then
64
+ * unregister every tool this server still owns.
62
65
  */
63
66
  dispose(): Promise<void>;
64
67
  }
@@ -15,7 +15,8 @@
15
15
  import type { Context } from '@deepseek-ai/cordis';
16
16
  import z from '@deepseek-ai/schemastery';
17
17
  import type { ReconnectConfig } from './connection.ts';
18
- export type { McpResult } from './tools.ts';
18
+ export { createMcpToolDefinition } from './tools.ts';
19
+ export type { McpResult, McpToolDefinitionOptions } from './tools.ts';
19
20
  export type { ReconnectConfig, ResolvedReconnectPolicy } from './connection.ts';
20
21
  /** Cordis plugin name used by loader diagnostics. */
21
22
  export declare const name = "mcp-client";
@@ -39,10 +40,12 @@ export interface StdioConfig {
39
40
  env: Record<string, string>;
40
41
  /** Working directory for the child process. */
41
42
  cwd: string;
42
- /** Per-tool-call timeout in milliseconds. */
43
+ /** Timeout per tool call or resource request in milliseconds. */
43
44
  toolCallTimeoutMs: number;
44
45
  /** Fail plugin activation when the initial connection or tool synchronization fails. */
45
46
  failOnStartupError: boolean;
47
+ /** Maximum UTF-8 bytes of attributed server instructions (default 32768). */
48
+ maxInstructionBytes?: number;
46
49
  /** Automatic reconnect policy after a lost connection; omission uses the defaults. */
47
50
  reconnect?: ReconnectConfig;
48
51
  }
@@ -60,10 +63,12 @@ export interface StreamableHttpConfig {
60
63
  url: string;
61
64
  /** Additional headers attached to MCP requests. */
62
65
  headers: Record<string, string>;
63
- /** Per-tool-call timeout in milliseconds. */
66
+ /** Timeout per tool call or resource request in milliseconds. */
64
67
  toolCallTimeoutMs: number;
65
68
  /** Fail plugin activation when the initial connection or tool synchronization fails. */
66
69
  failOnStartupError: boolean;
70
+ /** Maximum UTF-8 bytes of attributed server instructions (default 32768). */
71
+ maxInstructionBytes?: number;
67
72
  /** Automatic reconnect policy after a lost connection; omission uses the defaults. */
68
73
  reconnect?: ReconnectConfig;
69
74
  }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Publish connection-owned MCP resources and literal server instructions.
3
+ *
4
+ * @module @deepseek-ai/dsh-mcp-client
5
+ */
6
+ import type { Context } from '@deepseek-ai/cordis';
7
+ import type { McpResourceProvider } from '@deepseek-ai/dsh-mcp-resources';
8
+ /** Connection-owned values used by the resource and prompt consumers. */
9
+ export interface ServerContext {
10
+ /** Resource access through the current connection generation. */
11
+ resources: McpResourceProvider;
12
+ /**
13
+ * Read the last successfully connected server's attributed instructions.
14
+ * @returns literal prompt text, or an empty string when no server instructions are active.
15
+ */
16
+ instructions(): string;
17
+ }
18
+ /**
19
+ * Contribute server context to the services enabled by this composition.
20
+ * @param ctx - server plugin's registration scope and effect owner.
21
+ * @param server - configured server identity.
22
+ * @param connection - live resource operations and successful instruction snapshot.
23
+ */
24
+ export declare function registerServerContext(ctx: Context, server: string, connection: ServerContext): void;
25
+ //# sourceMappingURL=server-context.d.ts.map
@@ -11,8 +11,9 @@
11
11
  *
12
12
  * @module
13
13
  */
14
- import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
14
+ import { type Client } from '@modelcontextprotocol/client';
15
15
  import type { Context } from '@deepseek-ai/cordis';
16
+ import type { ToolDefinition, ToolExecution } from '@deepseek-ai/dsh-tools';
16
17
  import type { JsonValue } from '@deepseek-ai/dsh-util-values';
17
18
  /** Resolved options relevant to tool bridging. */
18
19
  export interface ToolBridgeOptions {
@@ -48,9 +49,9 @@ export declare function publicToolName(serverName: string, rawName: string): str
48
49
  *
49
50
  * Two phases keep the swap safe:
50
51
  *
51
- * 1. Fetch: drain uncached `tools/list` pagination and build the full next
52
+ * 1. Fetch: let the SDK aggregate `tools/list` and build the full next
52
53
  * generation of `ToolDefinition`s under public names. Any failure here
53
- * (network error, duplicate raw name, repeated continuation cursor) rejects
54
+ * (network error or duplicate raw name) rejects
54
55
  * and leaves the previous generation registered untouched.
55
56
  * 2. Swap: dispose the previous generation, register the new one. A registry
56
57
  * conflict here can only mean a foreign registration squats on this
@@ -68,4 +69,34 @@ export declare function publicToolName(serverName: string, rawName: string): str
68
69
  * disposers — the exact set of live registrations owned by this server.
69
70
  */
70
71
  export declare function syncTools(client: Client, ctx: Context, opts: ToolBridgeOptions, previous: ToolDisposers): Promise<ToolDisposers>;
72
+ /** One upstream MCP tool and the callback that obtains its raw protocol result. */
73
+ export interface McpToolDefinitionOptions {
74
+ /** ToolRuntime name presented to the model. */
75
+ name: string;
76
+ /** Upstream name used in result diagnostics. */
77
+ rawName: string;
78
+ /** Upstream model-facing description. */
79
+ description: string;
80
+ /** Upstream JSON input schema. */
81
+ inputSchema: Record<string, unknown>;
82
+ /** Advertised structured output schema, when present. */
83
+ outputSchema?: unknown;
84
+ /** Whether the upstream tool requires the unsupported task execution extension. */
85
+ taskRequired?: boolean;
86
+ /**
87
+ * Obtain one raw MCP result from the provider.
88
+ * @param args - model arguments admitted by the ToolRuntime.
89
+ * @param execution - exact ToolRuntime invocation, including its Agent and cancellation.
90
+ * @returns the external result object, validated before content projection.
91
+ */
92
+ call(args: Record<string, unknown>, execution: ToolExecution): Promise<unknown>;
93
+ }
94
+ /**
95
+ * Adapt an upstream MCP tool to canonical values and durable image content.
96
+ * Registration, provider lifetime, deadlines, and transport belong to the caller.
97
+ * @param ctx - plugin context carrying optional attachment and model services.
98
+ * @param options - upstream tool fields and its raw-result callback.
99
+ * @returns the unregistered ToolRuntime definition.
100
+ */
101
+ export declare function createMcpToolDefinition(ctx: Context, options: McpToolDefinitionOptions): ToolDefinition;
71
102
  //# sourceMappingURL=tools.d.ts.map
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * @module
7
7
  */
8
- import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
8
+ import type { Transport } from '@modelcontextprotocol/client';
9
9
  import type { Config } from './index.ts';
10
10
  /**
11
11
  * Create an MCP transport from the resolved plugin config.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-mcp-client",
3
3
  "description": "MCP client bridge: connects to MCP servers and registers their tools on ctx.tools",
4
- "version": "0.1.5-rc.2",
4
+ "version": "0.1.6-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,30 +27,45 @@
27
27
  ],
28
28
  "license": "MIT",
29
29
  "peerDependencies": {
30
- "@deepseek-ai/dsh-attachment": "^0.1.5-rc.2",
31
- "@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
32
- "@deepseek-ai/dsh-scope": "^0.1.5-rc.2",
33
- "@deepseek-ai/dsh-subprocess": "^0.1.5-rc.2",
34
- "@deepseek-ai/dsh-timeout": "^0.1.5-rc.2",
35
- "@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
30
+ "@deepseek-ai/dsh-scope": "^0.1.6-alpha.2",
31
+ "@deepseek-ai/dsh-attachment": "^0.1.6-alpha.2",
32
+ "@deepseek-ai/dsh-llm": "^0.1.6-alpha.2",
33
+ "@deepseek-ai/dsh-timeout": "^0.1.6-alpha.2",
34
+ "@deepseek-ai/dsh-subprocess": "^0.1.6-alpha.2",
35
+ "@deepseek-ai/dsh-tools": "^0.1.6-alpha.2",
36
+ "@deepseek-ai/dsh-mcp-resources": "^0.1.6-alpha.2",
37
+ "@deepseek-ai/dsh-system-prompt": "^0.1.6-alpha.2",
36
38
  "@deepseek-ai/cordis": "^4.0.2"
37
39
  },
38
40
  "dependencies": {
39
- "@modelcontextprotocol/sdk": "^1.12.0",
40
- "zod": "^4.4.3",
41
- "@deepseek-ai/schemastery": "^3.18.2"
41
+ "@modelcontextprotocol/client": "2.0.0",
42
+ "@deepseek-ai/schemastery": "^3.18.2",
43
+ "@deepseek-ai/dsh-util-values": "^0.1.6-alpha.2"
42
44
  },
43
45
  "devDependencies": {
44
46
  "@modelcontextprotocol/server-everything": "^2026.7.4",
45
47
  "@modelcontextprotocol/server-filesystem": "^2026.7.4",
46
- "@deepseek-ai/dsh-attachment": "^0.1.5-rc.2",
47
- "@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
48
- "@deepseek-ai/dsh-scope": "^0.1.5-rc.2",
49
- "@deepseek-ai/dsh-subprocess": "^0.1.5-rc.2",
50
- "@deepseek-ai/dsh-attachment-local": "^0.1.5-rc.2",
51
- "@deepseek-ai/dsh-timeout": "^0.1.5-rc.2",
52
- "@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
48
+ "@modelcontextprotocol/server": "2.0.0",
49
+ "@modelcontextprotocol/node": "2.0.0",
50
+ "zod": "^4.4.3",
51
+ "@deepseek-ai/dsh-attachment": "^0.1.6-alpha.2",
52
+ "@deepseek-ai/dsh-attachment-local": "^0.1.6-alpha.2",
53
+ "@deepseek-ai/dsh-llm": "^0.1.6-alpha.2",
54
+ "@deepseek-ai/dsh-scope": "^0.1.6-alpha.2",
55
+ "@deepseek-ai/dsh-timeout": "^0.1.6-alpha.2",
56
+ "@deepseek-ai/dsh-tools": "^0.1.6-alpha.2",
53
57
  "@deepseek-ai/cordis": "^4.0.2",
54
- "@deepseek-ai/dsh-http-proxy": "^0.1.5-rc.2"
58
+ "@deepseek-ai/dsh-http-proxy": "^0.1.6-alpha.2",
59
+ "@deepseek-ai/dsh-mcp-resources": "^0.1.6-alpha.2",
60
+ "@deepseek-ai/dsh-system-prompt": "^0.1.6-alpha.2",
61
+ "@deepseek-ai/dsh-subprocess": "^0.1.6-alpha.2"
62
+ },
63
+ "peerDependenciesMeta": {
64
+ "@deepseek-ai/dsh-mcp-resources": {
65
+ "optional": true
66
+ },
67
+ "@deepseek-ai/dsh-system-prompt": {
68
+ "optional": true
69
+ }
55
70
  }
56
71
  }