@aiwayds/dsh-mcp-adapter 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -0
- package/README.zh.md +82 -0
- package/cordis.patch.yml +33 -0
- package/lib/index.d.ts +342 -0
- package/lib/index.js +623 -0
- package/lib/index.js.map +1 -0
- package/package.json +46 -0
- package/scripts/link-dsh-closure.mjs +91 -0
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# dsh-mcp-adapter
|
|
2
|
+
|
|
3
|
+
English | [简体中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
Token-efficient MCP adapter for [DeepSeek Harness (dsh)](https://github.com/deepseek-ai/deepseek-harness) — a **prompt-side shim** inspired by [pi-mcp-adapter](https://github.com/nicobailon/pi-mcp-adapter).
|
|
6
|
+
|
|
7
|
+
## The problem
|
|
8
|
+
|
|
9
|
+
The official `@deepseek-ai/dsh-mcp-client` plugin registers every discovered MCP tool natively (`mcp__<server>__<tool>`), so **every request pays the full JSON Schema of every MCP tool** — the upstream README states this outright: *"Data-dependent schema cost is paid on every request while the tools are registered."* With a handful of servers and dozens of tools, that is thousands of tokens burned per message, whether or not the model ever calls them.
|
|
10
|
+
|
|
11
|
+
## The approach
|
|
12
|
+
|
|
13
|
+
This plugin keeps the official `dsh-mcp-client` as the connection layer (transports, reconnect, `tools/list_changed` re-sync — all upstream) and intervenes only at prompt assembly:
|
|
14
|
+
|
|
15
|
+
- every `mcp__*` tool schema is **folded out** of the assembled prompt (`system-prompt/assemble` waterfall);
|
|
16
|
+
- two **constant meta-tools** take their place, so standing prompt cost is O(1) in the number of servers/tools:
|
|
17
|
+
- **`mcp_list`** — compact catalog (tool names + truncated descriptions, no schemas); pass `tool` to expand one tool's full schema on demand, `server` to filter, `verbose` for everything;
|
|
18
|
+
- **`mcp_call`** — dispatch `{ tool, arguments }` to the still-registered definition, passing the run context through.
|
|
19
|
+
|
|
20
|
+
Tools stay registered in `ctx.tools`, so TUI rendering and `tools.restrict()` masking keep working — only the prompt payload changes. A folded, constant tool list is also friendlier to KV-prefix caching than upstream's per-resync generation swap.
|
|
21
|
+
|
|
22
|
+
One pipeline nuance: pre-execute / guard / post-execute stages that match by the child tool's name (`mcp__server__tool`) never fire for folded calls — the registry only sees the outer `mcp_call`. To gate MCP usage (approvals, policy), guard **`mcp_call` itself**.
|
|
23
|
+
|
|
24
|
+
Image results keep their native behavior: `mcp_call` delegates `output.render` to the dispatched child and forwards the child's `finalizeContent` with the exact same run-execution object, so image-bearing MCP results still project to durable attachment references instead of inlining base64 into the context.
|
|
25
|
+
|
|
26
|
+
**Fail-open:** if the meta-tools are not registered (name collision, partial startup), the adapter leaves the assembly untouched — you fall back to official full passthrough, never to undiscoverable tools.
|
|
27
|
+
|
|
28
|
+
**Code Mode:** under `mode: 'code'` the wire already collapses to `run_code`; this plugin is a no-op there.
|
|
29
|
+
|
|
30
|
+
**Load position:** loaded through host composition (the `cordis.patch.yml` `insert` below) the adapter is global — every agent's assemblies are folded. Loaded through an agent-scoped context instead, it applies only to that agent.
|
|
31
|
+
|
|
32
|
+
## Setup
|
|
33
|
+
|
|
34
|
+
Keep (or add) your `@deepseek-ai/dsh-mcp-client` lines in `cordis.patch.yml`, then add this plugin next to them:
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
- insert:
|
|
38
|
+
- id: dsh-mcp-adapter
|
|
39
|
+
name: '@aiwayds/dsh-mcp-adapter'
|
|
40
|
+
config: {}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Install:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
dsh plugin --profile <name> add @aiwayds/dsh-mcp-adapter
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or directly from git:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
dsh plugin --profile <name> add github:fan56/dsh-mcp-adapter
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Config
|
|
56
|
+
|
|
57
|
+
| key | default | meaning |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `prefix` | `"mcp__"` | tool-name prefix to fold |
|
|
60
|
+
| `keep` | `[]` | name patterns (`*` wildcard) kept native — pi-mcp-adapter's "direct mode", for high-frequency tools that deserve first-class schemas |
|
|
61
|
+
| `servers` | `[]` | server-name whitelist: when non-empty, only these servers' tools are folded / cataloged / dispatchable (all three consult the same list) |
|
|
62
|
+
| `descriptionLimit` | `200` | max chars per tool description in the `mcp_list` catalog |
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
config:
|
|
66
|
+
keep:
|
|
67
|
+
- mcp__fs__read_file
|
|
68
|
+
- mcp__github__*
|
|
69
|
+
servers:
|
|
70
|
+
- fs
|
|
71
|
+
- github
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Trust boundary:** by default every `prefix`-matching tool is folded — the prefix is a naming convention, not a security boundary, so tools registered by third-party plugins that happen to use `mcp__*` names fold too. To trust only the official client's servers, list them explicitly in `servers`; everything else stays native (still callable directly, just outside the meta-tools).
|
|
75
|
+
|
|
76
|
+
## Notes
|
|
77
|
+
|
|
78
|
+
- `mcp_call` only accepts `prefix`-matching tools (and, when `servers` is set, whitelisted servers) — it can never be used to bypass another tool's own pre-execute pipeline.
|
|
79
|
+
- Known boundary (waterfall order): an assemble listener registered **before** this plugin that adds `mcp__*` schemas after its own `next()` would escape the fold — this plugin folds what the assembled prompt contains when its listener runs. No such listener exists upstream today.
|
|
80
|
+
- Server names in the catalog are derived heuristically as the first `__`-delimited segment after the prefix (server names are `[A-Za-z0-9_-]{1,32}`, so a literal `__` inside a server name would mis-group).
|
|
81
|
+
- Coexists with [ben7am1n/dsh-mcp-proxy](https://github.com/ben7am1n/dsh-mcp-proxy) (connection-side proxy with its own servers — different, non-colliding tool names). That project credits pi-mcp-adapter as prior art too; this repo is an independent prompt-side take that reuses the official client instead of re-implementing connections.
|
|
82
|
+
- Trade-offs (same as pi-mcp-adapter): one extra discovery round-trip before the first call, and expanded schemas still occupy context once the model pulls them in.
|
|
83
|
+
|
|
84
|
+
## Development
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
npm install && npm run check && npm test
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`@deepseek-ai/*` types resolve from the global dsh closure via `scripts/link-dsh-closure.mjs` (run automatically by `precheck`) — they are deliberately absent from `package.json` so a single cordis instance exists in the type graph. See `DESIGN.md` for the full design rationale and upstream references.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# dsh-mcp-adapter
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 简体中文
|
|
4
|
+
|
|
5
|
+
面向 [DeepSeek Harness (dsh)](https://github.com/deepseek-ai/deepseek-harness) 的省 token MCP 适配器——一个受 [pi-mcp-adapter](https://github.com/nicobailon/pi-mcp-adapter) 启发的 **prompt-side shim**(提示词侧垫片)。
|
|
6
|
+
|
|
7
|
+
## 问题
|
|
8
|
+
|
|
9
|
+
官方 `@deepseek-ai/dsh-mcp-client` 插件把发现的每个 MCP 工具都原生注册进工具表(`mcp__<server>__<tool>`),于是**每次请求都要为每个 MCP 工具的完整 JSON Schema 付费**——上游 README 原话:*"Data-dependent schema cost is paid on every request while the tools are registered."* 几个 server、几十个工具下来,就是每条消息烧掉数千 token,无论模型是否真的调用它们。
|
|
10
|
+
|
|
11
|
+
## 思路
|
|
12
|
+
|
|
13
|
+
本插件完全保留官方 `dsh-mcp-client` 作为连接层(transport、自动重连、`tools/list_changed` 重同步——全是上游现成的),只在提示词装配这一处介入:
|
|
14
|
+
|
|
15
|
+
- 每个匹配的 `mcp__*` 工具 schema 被**折叠出**装配后的 prompt(`system-prompt/assemble` waterfall);
|
|
16
|
+
- 原位换上两个**恒定 meta-tool**,常驻 prompt 成本对 server/工具数量而言是 O(1):
|
|
17
|
+
- **`mcp_list`** — 紧凑目录(工具名 + 截断描述,不含 schema);传 `tool` 按需展开单个工具的完整 schema,传 `server` 过滤,传 `verbose` 全量内联;
|
|
18
|
+
- **`mcp_call`** — 按 `{ tool, arguments }` 把调用分发到仍然注册着的定义上,运行上下文原样透传。
|
|
19
|
+
|
|
20
|
+
工具本身仍注册在 `ctx.tools` 里,所以 TUI 渲染、`tools.restrict()` 掩蔽照常工作——变的只是 prompt 载荷。折叠后恒定的工具列表也比上游"每次重同步就换代"的模式更利于 KV 前缀缓存。
|
|
21
|
+
|
|
22
|
+
一个管线细节:按子工具名(`mcp__server__tool`)匹配的 pre-execute / guard / post-execute 阶段不会在折叠调用上触发——注册表只会看到外层的 `mcp_call`。要管控 MCP 使用(审批、策略),请 guard **`mcp_call` 本身**。
|
|
23
|
+
|
|
24
|
+
图片结果保持原生行为:`mcp_call` 把 `output.render` 委托给被分发的子工具,并以同一个执行对象转发子工具的 `finalizeContent`——带图 MCP 结果仍会投影为持久附件引用,而不是把 base64 内联进上下文。
|
|
25
|
+
|
|
26
|
+
**故障放行(fail-open):** 若两个 meta-tool 未成功注册(重名冲突、启动中断),本插件不动装配结果——退回官方全量直通,绝不会让 MCP 工具变得不可发现。
|
|
27
|
+
|
|
28
|
+
**Code Mode:** 在 `mode: 'code'` 下线上本来就折叠为 `run_code`,本插件天然 no-op。
|
|
29
|
+
|
|
30
|
+
**加载位置:** 经宿主组合加载(即下方 `cordis.patch.yml` 的 `insert` 行)时全局生效——所有 agent 的装配都会被折叠;若经某个 agent 的 scoped context 加载,则只对该 agent 生效。
|
|
31
|
+
|
|
32
|
+
## 安装
|
|
33
|
+
|
|
34
|
+
保留(或新增)你的 `@deepseek-ai/dsh-mcp-client` 配置行,然后把本插件加在同处:
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
- insert:
|
|
38
|
+
- id: dsh-mcp-adapter
|
|
39
|
+
name: '@aiwayds/dsh-mcp-adapter'
|
|
40
|
+
config: {}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
dsh plugin --profile <name> add @aiwayds/dsh-mcp-adapter
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 配置
|
|
48
|
+
|
|
49
|
+
| 键 | 默认值 | 含义 |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| `prefix` | `"mcp__"` | 要折叠的工具名前缀 |
|
|
52
|
+
| `keep` | `[]` | 保持原生进 prompt 的名字模式(`*` 通配)——对应 pi-mcp-adapter 的 direct 模式,适合高频、值得占一等座 schema 的工具 |
|
|
53
|
+
| `servers` | `[]` | server 白名单:非空时只有这些 server 的工具会被折叠 / 进目录 / 可分发(三处共用同一份名单) |
|
|
54
|
+
| `descriptionLimit` | `200` | `mcp_list` 目录里每条工具描述的最大字符数 |
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
config:
|
|
58
|
+
keep:
|
|
59
|
+
- mcp__fs__read_file
|
|
60
|
+
- mcp__github__*
|
|
61
|
+
servers:
|
|
62
|
+
- fs
|
|
63
|
+
- github
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**信任边界:** 默认所有匹配 `prefix` 的工具都会被折叠——前缀只是命名约定而非安全边界,第三方插件恰好用 `mcp__*` 注册的工具同样会折叠。若只信任官方 client 的 server,请在 `servers` 里显式列出;其余保持原生(仍可直调,只是不走 meta-tool)。
|
|
67
|
+
|
|
68
|
+
## 备注
|
|
69
|
+
|
|
70
|
+
- `mcp_call` 只接受匹配 `prefix` 的工具(配置了 `servers` 时还须在白名单内)——它不可能被用来绕过其它工具自己的 pre-execute 管线。
|
|
71
|
+
- 已知边界(waterfall 次序):若某 listener 注册**早于**本插件、并在自己的 `next()` 之后补插 `mcp__*` schema,该 schema 会逃过折叠——本插件折叠的是它运行时装配结果里的内容。当前上游不存在这样的 listener。
|
|
72
|
+
- 目录里的 server 名是启发式提取:前缀后第一段 `__` 分隔段(server 名规范为 `[A-Za-z0-9_-]{1,32}`,不会含字面 `__`,故不会错分组)。
|
|
73
|
+
- 与 [ben7am1n/dsh-mcp-proxy](https://github.com/ben7am1n/dsh-mcp-proxy) 可共存(它是 connection-side 代理、自带连接管理,工具名互不冲突)。该项目同样致谢 pi-mcp-adapter;本仓库是独立的 prompt-side 实现:复用官方 client 而不是重造连接层。
|
|
74
|
+
- 权衡(与 pi-mcp-adapter 相同):首次调用多一次发现往返;模型展开过的 schema 会占据后续上下文。
|
|
75
|
+
|
|
76
|
+
## 开发
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
npm install && npm run check && npm test
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`@deepseek-ai/*` 类型由 `scripts/link-dsh-closure.mjs` 从全局 dsh 闭包软链解析(`precheck` 自动执行)——它们被刻意排除在 `package.json` 之外,以保证类型图中只存在一份 cordis 实例。完整设计依据与上游参考见 `DESIGN.md`。
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# dsh-mcp-adapter bundle patch: mounts the prompt-side MCP folding shim
|
|
2
|
+
# under the stable id `dsh-mcp-adapter`.
|
|
3
|
+
#
|
|
4
|
+
# It keeps the official @deepseek-ai/dsh-mcp-client plugin instances as the
|
|
5
|
+
# connection layer (transports, reconnect, list_changed re-sync all stay
|
|
6
|
+
# upstream), but folds every `mcp__<server>__<tool>` schema out of each
|
|
7
|
+
# assembled prompt and exposes two constant meta-tools (`mcp_list` /
|
|
8
|
+
# `mcp_call`) instead — the model expands schemas on demand, so standing
|
|
9
|
+
# prompt cost is O(1) in the number of MCP servers and tools.
|
|
10
|
+
#
|
|
11
|
+
# Load order does not matter: folding happens per prompt assembly, and the
|
|
12
|
+
# adapter degrades to official passthrough when its own meta-tools are not
|
|
13
|
+
# registered. List it next to your dsh-mcp-client lines for clarity. Under
|
|
14
|
+
# Code Mode the wire already collapses to `run_code`, so this plugin is a
|
|
15
|
+
# no-op there.
|
|
16
|
+
#
|
|
17
|
+
# Config (all optional):
|
|
18
|
+
# prefix: tool-name prefix to fold (default "mcp__")
|
|
19
|
+
# keep: name patterns ("*" wildcard) kept native, e.g.
|
|
20
|
+
# ["mcp__fs__read_file", "mcp__github__*"]
|
|
21
|
+
# servers: server-name whitelist; when non-empty only these
|
|
22
|
+
# servers' tools are folded/cataloged/dispatchable
|
|
23
|
+
# (default [] = every prefix-matching tool; the
|
|
24
|
+
# prefix alone is a naming convention, not a trust
|
|
25
|
+
# boundary — list servers to trust only the official
|
|
26
|
+
# client's registrations)
|
|
27
|
+
# descriptionLimit: max chars per tool description in the mcp_list
|
|
28
|
+
# catalog (default 200)
|
|
29
|
+
|
|
30
|
+
- insert:
|
|
31
|
+
- id: dsh-mcp-adapter
|
|
32
|
+
name: '@aiwayds/dsh-mcp-adapter'
|
|
33
|
+
config: {}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-mcp-adapter — prompt-side MCP folding shim for dsh.
|
|
3
|
+
*
|
|
4
|
+
* The official `@deepseek-ai/dsh-mcp-client` registers every discovered MCP
|
|
5
|
+
* tool natively (`mcp__<server>__<tool>`), so every assembled prompt carries
|
|
6
|
+
* the full JSON Schema of every MCP tool. This plugin keeps that client as
|
|
7
|
+
* the connection layer (transport, reconnect, `list_changed` re-sync — all
|
|
8
|
+
* upstream) and intervenes only at prompt assembly:
|
|
9
|
+
*
|
|
10
|
+
* - every prefix-matching tool schema is folded out of the assembled prompt
|
|
11
|
+
* (`system-prompt/assemble` waterfall), so standing prompt cost is O(1) in
|
|
12
|
+
* the number of servers and tools, and the tool list stays KV-prefix
|
|
13
|
+
* stable across server re-syncs. The prefix is a naming convention, not a
|
|
14
|
+
* trust boundary — third-party tools that happen to use it fold too; set
|
|
15
|
+
* `servers` to restrict folding (and cataloging/dispatch) to named servers;
|
|
16
|
+
* - two constant meta-tools replace them:
|
|
17
|
+
* - `mcp_list` — compact catalog grouped by server; `tool` expands one
|
|
18
|
+
* full schema on demand, `server` filters, `verbose` inlines everything;
|
|
19
|
+
* - `mcp_call` — dispatches `{ tool, arguments }` to the still-registered
|
|
20
|
+
* definition, forwarding the run context.
|
|
21
|
+
*
|
|
22
|
+
* Tools stay registered in `ctx.tools`, so TUI rendering and
|
|
23
|
+
* `tools.restrict()` masking keep working — only the prompt payload changes.
|
|
24
|
+
* Pipeline nuance: pre-execute/guard/post-execute stages that match by the
|
|
25
|
+
* child tool's name never fire for folded calls (the registry only sees the
|
|
26
|
+
* outer `mcp_call`) — gate MCP usage by guarding `mcp_call` itself.
|
|
27
|
+
*
|
|
28
|
+
* Child-owned projections are preserved: `mcp_call` delegates its
|
|
29
|
+
* `output.render` to the dispatched child and forwards the child's
|
|
30
|
+
* `finalizeContent` with the exact same run-execution object, so
|
|
31
|
+
* image-bearing MCP results still project to durable attachment references
|
|
32
|
+
* instead of inlining base64 into the context.
|
|
33
|
+
*
|
|
34
|
+
* Fail-open: unless BOTH meta-tools resolve (in the assembling scope) to
|
|
35
|
+
* exactly this plugin's definitions, assemblies pass through untouched — a
|
|
36
|
+
* broken adapter must never make MCP tools undiscoverable. Under Code Mode
|
|
37
|
+
* the wire already collapses to `run_code`, so this plugin is a natural no-op.
|
|
38
|
+
*
|
|
39
|
+
* All decision logic is exported as pure functions; the only Cordis calls
|
|
40
|
+
* live in {@link apply}.
|
|
41
|
+
*
|
|
42
|
+
* @module @aiwayds/dsh-mcp-adapter
|
|
43
|
+
*/
|
|
44
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
45
|
+
import z from '@deepseek-ai/schemastery';
|
|
46
|
+
import type { ContentBlock, ToolSchema } from '@deepseek-ai/dsh-llm';
|
|
47
|
+
import type { ToolDefinition, ToolExecution, ToolExecutionResult } from '@deepseek-ai/dsh-tools';
|
|
48
|
+
import type { ScopeKey } from '@deepseek-ai/dsh-scope';
|
|
49
|
+
/** Cordis plugin name used by loader diagnostics. */
|
|
50
|
+
export declare const name = "mcp-adapter";
|
|
51
|
+
/** Services required by this plugin. */
|
|
52
|
+
export declare const inject: string[];
|
|
53
|
+
/** Registered name of the catalog meta-tool. */
|
|
54
|
+
export declare const MCP_LIST_TOOL_NAME = "mcp_list";
|
|
55
|
+
/** Registered name of the dispatch meta-tool. */
|
|
56
|
+
export declare const MCP_CALL_TOOL_NAME = "mcp_call";
|
|
57
|
+
/** Resolved adapter configuration. */
|
|
58
|
+
export interface AdapterConfig {
|
|
59
|
+
/** Tool-name prefix to fold out of assembled prompts. */
|
|
60
|
+
prefix: string;
|
|
61
|
+
/** Name patterns ("*" wildcard) kept native in the prompt. */
|
|
62
|
+
keep: string[];
|
|
63
|
+
/**
|
|
64
|
+
* Server-name whitelist. Empty (default) folds every prefix-matching tool —
|
|
65
|
+
* the prefix is then a naming convention, not a trust boundary. Non-empty
|
|
66
|
+
* folds/catalogs/dispatches ONLY tools of the listed servers; fold, catalog
|
|
67
|
+
* and dispatch all consult the same list.
|
|
68
|
+
*/
|
|
69
|
+
servers: string[];
|
|
70
|
+
/** Max chars per tool description in the mcp_list catalog. */
|
|
71
|
+
descriptionLimit: number;
|
|
72
|
+
}
|
|
73
|
+
export declare const Config: z<AdapterConfig>;
|
|
74
|
+
/**
|
|
75
|
+
* Whether a tool name matches any keep pattern ("*" wildcard, anchored;
|
|
76
|
+
* a pattern without wildcards is an exact name).
|
|
77
|
+
* @param name - the registered tool name.
|
|
78
|
+
* @param patterns - keep patterns from config.
|
|
79
|
+
* @returns whether the tool stays native in the prompt.
|
|
80
|
+
*/
|
|
81
|
+
export declare function matchesKeep(name: string, patterns: readonly string[]): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Whether the server segment of one prefix-matching tool name passes the
|
|
84
|
+
* optional servers whitelist. An empty list admits every server (the prefix
|
|
85
|
+
* is a naming convention, not a trust boundary); a non-empty list admits
|
|
86
|
+
* only the named servers.
|
|
87
|
+
* @param name - the registered tool name.
|
|
88
|
+
* @param prefix - the configured MCP prefix.
|
|
89
|
+
* @param servers - server-name whitelist from config.
|
|
90
|
+
* @returns whether the tool's server is admitted.
|
|
91
|
+
*/
|
|
92
|
+
export declare function isAllowedServer(name: string, prefix: string, servers: readonly string[]): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Whether one tool's schema should be folded out of the prompt: it must
|
|
95
|
+
* carry the configured prefix, belong to a whitelisted server, not match the
|
|
96
|
+
* keep list — and it must never be one of this plugin's own meta-tools,
|
|
97
|
+
* whatever the prefix is configured to (folding them would make every folded
|
|
98
|
+
* MCP tool undiscoverable).
|
|
99
|
+
* @param name - the registered tool name.
|
|
100
|
+
* @param prefix - tool-name prefix to fold.
|
|
101
|
+
* @param keep - keep patterns from config.
|
|
102
|
+
* @param servers - server-name whitelist from config (default: no filtering).
|
|
103
|
+
* @returns whether the schema leaves the prompt (the tool stays callable).
|
|
104
|
+
*/
|
|
105
|
+
export declare function shouldFold(name: string, prefix: string, keep: readonly string[], servers?: readonly string[]): boolean;
|
|
106
|
+
/** Folding knobs resolved from config. */
|
|
107
|
+
export interface FoldOptions {
|
|
108
|
+
/** Tool-name prefix to fold. */
|
|
109
|
+
prefix: string;
|
|
110
|
+
/** Name patterns kept native. */
|
|
111
|
+
keep: readonly string[];
|
|
112
|
+
/** Server-name whitelist; empty or omitted admits every server. */
|
|
113
|
+
servers?: readonly string[];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Structural subset of `PromptAssembly` the folding operates on, so tests
|
|
117
|
+
* can drive it without importing prompt-assembly types.
|
|
118
|
+
*/
|
|
119
|
+
export interface AssemblyWithTools {
|
|
120
|
+
sections: unknown[];
|
|
121
|
+
contexts: unknown[];
|
|
122
|
+
tools: ToolSchema[];
|
|
123
|
+
variables: Record<string, string | undefined>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Rewrite one assembled prompt: drop the schemas of foldable tools, keep
|
|
127
|
+
* everything else (sections, contexts, variables, non-MCP tools, meta-tools)
|
|
128
|
+
* untouched.
|
|
129
|
+
*
|
|
130
|
+
* Fail-open contract: with `metaToolsLive` false the assembly object is
|
|
131
|
+
* returned AS-IS (same reference) — folding is only safe while the model can
|
|
132
|
+
* still discover MCP tools through the meta-tools.
|
|
133
|
+
*
|
|
134
|
+
* @param assembly - the post-waterfall assembly.
|
|
135
|
+
* @param options - folding knobs.
|
|
136
|
+
* @param metaToolsLive - whether both meta-tools currently resolve to this
|
|
137
|
+
* plugin's own definitions in the assembling scope.
|
|
138
|
+
* @returns the folded assembly, or the input unchanged when nothing folds.
|
|
139
|
+
*/
|
|
140
|
+
export declare function foldPromptAssembly<T extends AssemblyWithTools>(assembly: T, options: FoldOptions, metaToolsLive: boolean): T;
|
|
141
|
+
/** Options for the catalog builder. */
|
|
142
|
+
export interface McpListOptions {
|
|
143
|
+
/** Tool-name prefix identifying MCP tools. */
|
|
144
|
+
prefix: string;
|
|
145
|
+
/** Max chars per tool description in the catalog. */
|
|
146
|
+
descriptionLimit: number;
|
|
147
|
+
/** Server-name whitelist; empty or omitted admits every server. */
|
|
148
|
+
servers?: readonly string[];
|
|
149
|
+
}
|
|
150
|
+
/** Normalized `mcp_list` arguments. */
|
|
151
|
+
export interface McpListArgs {
|
|
152
|
+
/** Expand this tool's full schema (highest priority when set). */
|
|
153
|
+
tool?: string;
|
|
154
|
+
/** Filter the catalog to one server. */
|
|
155
|
+
server?: string;
|
|
156
|
+
/** Inline every tool's schema in the catalog. */
|
|
157
|
+
verbose?: boolean;
|
|
158
|
+
}
|
|
159
|
+
/** One catalog entry; `parameters` is present only when expanded. */
|
|
160
|
+
export interface CatalogToolEntry {
|
|
161
|
+
name: string;
|
|
162
|
+
description: string;
|
|
163
|
+
parameters?: Record<string, unknown>;
|
|
164
|
+
}
|
|
165
|
+
/** One server group in the compact catalog. */
|
|
166
|
+
export interface CatalogServerEntry {
|
|
167
|
+
server: string;
|
|
168
|
+
tools: CatalogToolEntry[];
|
|
169
|
+
}
|
|
170
|
+
/** `mcp_list`'s canonical result: catalog, single expansion, or error. */
|
|
171
|
+
export type McpListResult = {
|
|
172
|
+
servers: CatalogServerEntry[];
|
|
173
|
+
} | {
|
|
174
|
+
tool: {
|
|
175
|
+
name: string;
|
|
176
|
+
description: string;
|
|
177
|
+
parameters: Record<string, unknown>;
|
|
178
|
+
};
|
|
179
|
+
} | {
|
|
180
|
+
error: string;
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* Narrow arbitrary model arguments to the `mcp_list` shape, dropping
|
|
184
|
+
* unknown or mistyped keys instead of throwing (the model misbehaves).
|
|
185
|
+
* @param args - raw model arguments.
|
|
186
|
+
* @returns the normalized catalog query.
|
|
187
|
+
*/
|
|
188
|
+
export declare function normalizeMcpListArgs(args: unknown): McpListArgs;
|
|
189
|
+
/**
|
|
190
|
+
* Derive the server segment of a registered MCP tool name: the first
|
|
191
|
+
* `__`-delimited segment after the prefix. Heuristic, not a parse: server
|
|
192
|
+
* names are constrained to `[A-Za-z0-9_-]{1,32}` by the official client, so
|
|
193
|
+
* a literal `__` inside a server name (impossible there) would mis-group.
|
|
194
|
+
* @param name - the registered tool name.
|
|
195
|
+
* @param prefix - the configured MCP prefix.
|
|
196
|
+
* @returns the server segment ('' for degenerate names).
|
|
197
|
+
*/
|
|
198
|
+
export declare function serverOfToolName(name: string, prefix: string): string;
|
|
199
|
+
/**
|
|
200
|
+
* Truncate one description to the catalog limit, marking the cut with an
|
|
201
|
+
* ellipsis character (so one char of headroom is reserved). A cut that would
|
|
202
|
+
* land on the LOW half of a surrogate pair backs off one code unit so no
|
|
203
|
+
* orphaned high surrogate is emitted.
|
|
204
|
+
* @param text - the full tool description.
|
|
205
|
+
* @param limit - max chars in the catalog.
|
|
206
|
+
* @returns the truncated description.
|
|
207
|
+
*/
|
|
208
|
+
export declare function truncateDescription(text: string, limit: number): string;
|
|
209
|
+
/**
|
|
210
|
+
* Build the `mcp_list` result over the currently visible schemas.
|
|
211
|
+
*
|
|
212
|
+
* Priority: `tool` (full single-tool expansion) > catalog (optionally
|
|
213
|
+
* filtered by `server`, optionally `verbose`). Unknown or non-MCP `tool`,
|
|
214
|
+
* and any query that selects nothing, return a structured error — never a
|
|
215
|
+
* throw — so the model can correct itself.
|
|
216
|
+
*
|
|
217
|
+
* @param args - the normalized catalog query.
|
|
218
|
+
* @param schemas - schemas visible to the calling agent.
|
|
219
|
+
* @param options - catalog knobs.
|
|
220
|
+
* @returns the canonical result value.
|
|
221
|
+
*/
|
|
222
|
+
export declare function buildMcpListResult(args: McpListArgs, schemas: readonly ToolSchema[], options: McpListOptions): McpListResult;
|
|
223
|
+
/** Structural slice of a resolvable tool: what dispatch actually consumes. */
|
|
224
|
+
export interface DispatchableTool<E> {
|
|
225
|
+
/** Cooperative timeout budget declared by the tool, if any. */
|
|
226
|
+
readonly timeoutMs?: number;
|
|
227
|
+
/** The tool body dispatch invokes. */
|
|
228
|
+
execute(args: unknown, exec: E): Promise<unknown>;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Dispatch one `mcp_call` to the resolved child tool.
|
|
232
|
+
*
|
|
233
|
+
* Validation: `tool` must be a non-empty string matching the configured
|
|
234
|
+
* prefix, must not name a meta-tool, must belong to a whitelisted server
|
|
235
|
+
* (when `servers` is non-empty), and must resolve through `resolve`
|
|
236
|
+
* (scope-aware — a restricted-away tool does not resolve, so whitelists are
|
|
237
|
+
* respected naturally). Non-MCP tools are REFUSED outright: dispatching them
|
|
238
|
+
* here would bypass their own pre-execute pipeline (guards, approvals).
|
|
239
|
+
*
|
|
240
|
+
* Execution: `definition.execute(args.arguments ?? {}, exec)` with the
|
|
241
|
+
* meta-tool's own run context passed through verbatim — the official MCP
|
|
242
|
+
* executor consumes `exec.signal` (per-request abort) and `exec.agent`
|
|
243
|
+
* (model-route resolution for image admission), both carried by that same
|
|
244
|
+
* context object.
|
|
245
|
+
*
|
|
246
|
+
* Timeout: no timer is raced for the official mcp-client tools — their
|
|
247
|
+
* executor already self-times every MCP request (`tools/call` is sent with
|
|
248
|
+
* `timeout: toolCallTimeoutMs`, verified in the upstream bridge source), and
|
|
249
|
+
* they never declare `timeoutMs`. Only a definition that DECLARED
|
|
250
|
+
* `timeoutMs` gets a raced deadline, because calling `definition.execute()`
|
|
251
|
+
* directly bypasses the registry's timeout policy wrapper.
|
|
252
|
+
*
|
|
253
|
+
* Errors never throw out of dispatch: a child rejection (or raced timeout)
|
|
254
|
+
* is wrapped into a structured `{ error }` the model can correct and retry.
|
|
255
|
+
*
|
|
256
|
+
* @param args - raw model arguments (`{ tool, arguments? }`, validated here).
|
|
257
|
+
* @param prefix - the configured MCP prefix.
|
|
258
|
+
* @param resolve - scope-aware tool resolver (production: `ctx.tools.get`).
|
|
259
|
+
* @param exec - the meta-tool's run context, forwarded to the child.
|
|
260
|
+
* @param servers - server-name whitelist (default: no filtering).
|
|
261
|
+
* @returns the child's resolved value verbatim, or a structured error.
|
|
262
|
+
*/
|
|
263
|
+
export declare function dispatchMcpCall<E>(args: unknown, prefix: string, resolve: (name: string) => DispatchableTool<E> | undefined, exec: E, servers?: readonly string[]): Promise<unknown>;
|
|
264
|
+
/**
|
|
265
|
+
* Render one mcp_call outcome by delegating to the dispatched child's own
|
|
266
|
+
* `output.render`, so model-facing text is exactly what a native call of the
|
|
267
|
+
* child would produce. Falls back to the generic JSON rendering whenever the
|
|
268
|
+
* value is this plugin's structured-error wrap, the child cannot be resolved
|
|
269
|
+
* from the arguments, or the child's render throws (the value's shape is the
|
|
270
|
+
* child's contract, not this tool's).
|
|
271
|
+
* @param metaArgs - the frozen mcp_call arguments (`{ tool, arguments? }`).
|
|
272
|
+
* @param value - the dispatched child's resolved value (or an error wrap).
|
|
273
|
+
* @param resolveChild - definition resolver (unscoped view is fine here —
|
|
274
|
+
* render is a pure display projection).
|
|
275
|
+
* @returns the model-facing content blocks.
|
|
276
|
+
*/
|
|
277
|
+
export declare function renderDispatched(metaArgs: unknown, value: unknown, resolveChild: (name: string) => ToolDefinition | undefined): ContentBlock[];
|
|
278
|
+
/**
|
|
279
|
+
* Forward one successful mcp_call outcome to the dispatched child's own
|
|
280
|
+
* `finalizeContent`, restoring child-owned rich projections (image
|
|
281
|
+
* attachments).
|
|
282
|
+
*
|
|
283
|
+
* Why this is needed: the official mcp-client stages image projections in a
|
|
284
|
+
* `WeakMap<ToolExecution, PreparedProjection>` filled by its executor
|
|
285
|
+
* (`packages/mcp/mcp-client/src/tools.ts:255`, `:354-358`) and hands them
|
|
286
|
+
* back from `finalizeContent` (`:262-270`) — but the registry invokes
|
|
287
|
+
* `finalizeContent` only for the OUTER executed definition
|
|
288
|
+
* (`packages/core/tools/src/index.ts:1649-1654`), which here is `mcp_call`.
|
|
289
|
+
* Without forwarding, the child's projection never runs and image results
|
|
290
|
+
* degrade to their base64-in-text fallback.
|
|
291
|
+
*
|
|
292
|
+
* Mechanics: the child is re-resolved from `metaArgs.tool`; a child-view
|
|
293
|
+
* success result is synthesized with `content` computed by the child's own
|
|
294
|
+
* `output.render` (the same fallback the registry would have produced for a
|
|
295
|
+
* native call, so the child's value/fallback equality guards still pass);
|
|
296
|
+
* and the child's `finalizeContent` is invoked with the EXACT `exec` object
|
|
297
|
+
* dispatch forwarded to `child.execute` — same object identity, so the
|
|
298
|
+
* child's WeakMap lookup hits.
|
|
299
|
+
*
|
|
300
|
+
* Failure paths never project: an `isError` result and this plugin's
|
|
301
|
+
* structured-error wraps return `undefined` immediately; a child without
|
|
302
|
+
* `finalizeContent`, an unresolvable child, or a throwing child callback
|
|
303
|
+
* (the contract demands total callbacks) also degrade to `undefined`, which
|
|
304
|
+
* preserves the registry-computed content.
|
|
305
|
+
*
|
|
306
|
+
* @param metaArgs - the frozen mcp_call arguments (`{ tool, arguments? }`).
|
|
307
|
+
* @param result - the registry-normalized mcp_call outcome.
|
|
308
|
+
* @param exec - the mcp_call execution object that was forwarded verbatim
|
|
309
|
+
* to the child body — the child's projection WeakMap key.
|
|
310
|
+
* @param resolveChild - scope-aware definition resolver.
|
|
311
|
+
* @returns replacement content from the child's finalizer, or `undefined`
|
|
312
|
+
* to preserve the registry-computed content.
|
|
313
|
+
*/
|
|
314
|
+
export declare function delegateFinalizeContent(metaArgs: unknown, result: Readonly<ToolExecutionResult>, exec: Readonly<ToolExecution>, resolveChild: (name: string) => ToolDefinition | undefined): ContentBlock[] | undefined;
|
|
315
|
+
/**
|
|
316
|
+
* Build the `mcp_list` meta-tool definition.
|
|
317
|
+
* @param options - catalog knobs (prefix, description limit).
|
|
318
|
+
* @param listSchemas - visible-schemas source (production: `ctx.tools.schemas`,
|
|
319
|
+
* called with the calling agent so restrictions are respected).
|
|
320
|
+
* @returns the complete tool definition.
|
|
321
|
+
*/
|
|
322
|
+
export declare function createMcpListTool(options: McpListOptions, listSchemas: (scope?: ScopeKey) => readonly ToolSchema[]): ToolDefinition;
|
|
323
|
+
/**
|
|
324
|
+
* Build the `mcp_call` meta-tool definition.
|
|
325
|
+
* @param prefix - the configured MCP prefix (dispatch boundary).
|
|
326
|
+
* @param resolve - scope-aware definition resolver (production:
|
|
327
|
+
* `ctx.tools.get`, called with the calling agent).
|
|
328
|
+
* @param servers - server-name whitelist (default: no filtering).
|
|
329
|
+
* @returns the complete tool definition.
|
|
330
|
+
*/
|
|
331
|
+
export declare function createMcpCallTool(prefix: string, resolve: (name: string, scope?: ScopeKey) => ToolDefinition | undefined, servers?: readonly string[]): ToolDefinition;
|
|
332
|
+
/**
|
|
333
|
+
* Register the two meta-tools and install the assembly folding listener.
|
|
334
|
+
*
|
|
335
|
+
* Failure safety: if either meta-tool registration fails (name collision),
|
|
336
|
+
* both registrations are rolled back, a warning is logged, and NO listener
|
|
337
|
+
* is installed — the official full-schema passthrough stays intact.
|
|
338
|
+
*
|
|
339
|
+
* @param ctx - plugin context carrying the tool registry.
|
|
340
|
+
* @param config - resolved adapter configuration.
|
|
341
|
+
*/
|
|
342
|
+
export declare function apply(ctx: Context, config: AdapterConfig): void;
|