@reinsjs/tools-mcp 0.1.1 → 0.2.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/CHANGELOG.md +22 -0
- package/README.md +88 -8
- package/dist/index.d.ts +9 -4
- package/dist/index.js +32 -9
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/{types-CUUDQMVs.d.ts → types-F_1Kfslp.d.ts} +19 -2
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @reinsjs/tools-mcp
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 14345eb: Every user-facing runtime string is now English. This covers thrown error messages (construction-time validation, store and registry errors, HTTP 4xx bodies), `warn()` output from the brain modules and the server, the text the model sees in error tool results (`Unknown tool: …`, `Tool call blocked: …`, `Invalid arguments: …`, `Approval denied…`, `Approval expired…`), the `note` / `when` fields of every lowering loss matrix and landing, the placeholder text for content a wire protocol cannot carry, the conformance suites exported from `@reinsjs/core/testing`, and the `@reinsjs/eval` report and gate output. Previously these were Chinese while the READMEs and model-facing prompts were English, which left a non-Chinese-speaking host with unreadable diagnostics.
|
|
8
|
+
|
|
9
|
+
Nothing changes structurally: same errors, same codes, same warning points, same landing kinds. Hosts that match on the text of a message or a landing note (rather than on its error code or `landing` value) need to update those matches.
|
|
10
|
+
|
|
11
|
+
- 41db9c1: `httpTransport({ auth })` for credentials that expire. `headers` is fixed when the transport is built, but the connection is created lazily, reused across runs and rebuilt from the same recipe after a drop — so an expiring token cannot live there. `auth.token()` is called before every request and `auth.onUnauthorized()` on a 401, after which the request is retried once. Passing both `auth` and an `Authorization` header throws at construction time instead of letting one silently win.
|
|
12
|
+
|
|
13
|
+
`McpAuth` is two methods and references no MCP SDK type. It is deliberately not the SDK's `OAuthClientProvider`: that interface's core value is driving a browser authorization prompt, which a server-side agent cannot do. Obtain the tokens in your application and hand them over; call the SDK's `refreshAuthorization` from inside `onUnauthorized` if you want it.
|
|
14
|
+
|
|
15
|
+
README additions: "Expiring credentials", and "Gateway-style servers" — a warning that servers routing hundreds of operations through a single dispatching tool (TikTok for Business's `tool_execute` is the current example) silently defeat approval policies written against tool names, with the recipe for deciding on the operation name inside the arguments instead. The executable version is `packages/brain/src/gateway-tool.recipe.test.ts`, whose first case proves a name-based `deny` rule lets a delete through.
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [49d5dea]
|
|
20
|
+
- Updated dependencies [c16e3ea]
|
|
21
|
+
- Updated dependencies [14345eb]
|
|
22
|
+
- Updated dependencies [a082a0c]
|
|
23
|
+
- @reinsjs/core@0.2.0
|
|
24
|
+
|
|
3
25
|
## 0.1.1
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -36,8 +36,9 @@ const files = mcpTools({
|
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
The main entry uses only Web-standard APIs (`fetch`, `URL`, `Headers`) and runs on Cloudflare Workers without
|
|
39
|
-
`nodejs_compat` (verified on a 2023 compatibility date, see `spikes/edge-runtime-check`)
|
|
40
|
-
`
|
|
39
|
+
`nodejs_compat` (verified on a 2023 compatibility date, see `spikes/edge-runtime-check`), and on Bun, Deno
|
|
40
|
+
(`--allow-net` is enough) and Vercel's `edge-runtime` (`spikes/runtime-matrix` — `tools/list` + `tools/call`
|
|
41
|
+
against a local server in each). Only `/node` imports `node:*`.
|
|
41
42
|
|
|
42
43
|
## What happens at run time
|
|
43
44
|
|
|
@@ -102,20 +103,99 @@ accept that the paused run is abandoned and start a new one, or pass `allowConfi
|
|
|
102
103
|
that resume also emits the tool-change note. What the library will not do is silently resume with a different
|
|
103
104
|
tool table than the one the approver saw.
|
|
104
105
|
|
|
106
|
+
## Expiring credentials
|
|
107
|
+
|
|
108
|
+
A fixed token can live in `headers`. A token that expires cannot: `headers` is fixed when the transport is built,
|
|
109
|
+
while the connection is created lazily, reused across runs and rebuilt from the same recipe after a drop — so once
|
|
110
|
+
the token expires, even the rebuild carries the stale one. Pass `auth` instead:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
httpTransport({
|
|
114
|
+
url: "https://business-api.tiktok.com/open_mcp/tt-ads-mcp-layer",
|
|
115
|
+
auth: {
|
|
116
|
+
token: () => db.accessToken(userId), // called before every request
|
|
117
|
+
onUnauthorized: () => db.refresh(userId), // called on 401, then the request is retried once
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`auth` and an `Authorization` header are mutually exclusive — passing both throws at construction time rather than
|
|
123
|
+
letting one silently win.
|
|
124
|
+
|
|
125
|
+
`McpAuth` is deliberately these two methods and nothing more, and it does not reference any MCP SDK type. It is not
|
|
126
|
+
the SDK's `OAuthClientProvider`: the valuable part of that interface is driving a browser authorization prompt,
|
|
127
|
+
which a server-side agent cannot do. Obtain and store the tokens in your web application; the loop only reads and
|
|
128
|
+
refreshes them. Full OAuth flows are still not wrapped — call the SDK's `refreshAuthorization` (or your own code)
|
|
129
|
+
from inside `token` / `onUnauthorized`.
|
|
130
|
+
|
|
131
|
+
## Gateway-style servers
|
|
132
|
+
|
|
133
|
+
Some servers put hundreds of operations behind **one** dispatching tool. TikTok for Business is the current
|
|
134
|
+
example: its layered endpoint registers 41 ordinary tools plus `tool_list` / `tool_get` / `tool_execute`, and the
|
|
135
|
+
other ~330 operations live only in a server-side registry, reached through `tool_execute({ tool_name, params })`.
|
|
136
|
+
|
|
137
|
+
This costs the loop nothing — the tool table is fixed for the whole run, so the prompt-cache prefix and config hash
|
|
138
|
+
are unaffected. **But it silently defeats approval policies written against tool names.** Creating a campaign,
|
|
139
|
+
changing a budget and deleting an asset group are all called `tool_execute`; what actually happens is in the
|
|
140
|
+
arguments.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
// WRONG — this never matches. Every operation is named tool_execute.
|
|
144
|
+
approval({ deny: ["*_delete"], allow: ["*"] })
|
|
145
|
+
|
|
146
|
+
// RIGHT — decide on the operation name inside the arguments.
|
|
147
|
+
const operationOf = (args: unknown): string =>
|
|
148
|
+
typeof args === "object" && args !== null && "tool_name" in args
|
|
149
|
+
? String((args as { tool_name?: unknown }).tool_name ?? "")
|
|
150
|
+
: ""
|
|
151
|
+
|
|
152
|
+
approval({
|
|
153
|
+
ask: [{
|
|
154
|
+
id: "gateway.writes",
|
|
155
|
+
match: (call) =>
|
|
156
|
+
call.name === "tool_execute" && /_(create|update|delete|upload)$/.test(operationOf(call.args)),
|
|
157
|
+
summary: (call) => `TikTok ${operationOf(call.args)}`, // the approver sees the real operation
|
|
158
|
+
}],
|
|
159
|
+
allow: [
|
|
160
|
+
{ id: "gateway.reads", match: (call) =>
|
|
161
|
+
call.name === "tool_execute" && /_(get|list|search)$/.test(operationOf(call.args)) },
|
|
162
|
+
"tool_list",
|
|
163
|
+
"tool_get",
|
|
164
|
+
],
|
|
165
|
+
})
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The executable version of this recipe is `packages/brain/src/gateway-tool.recipe.test.ts`, including the negative
|
|
169
|
+
case that proves the name-based rule lets a delete through.
|
|
170
|
+
|
|
171
|
+
With no rules configured the default is safe but blunt: such a server declares no annotations, so the translated
|
|
172
|
+
tool carries no `risk`, and `unmatched: "byRisk"` sends **every** call — reads included — to a human. That is
|
|
173
|
+
fail-closed by design; open up the read path with the recipe above rather than by allowing the dispatcher wholesale.
|
|
174
|
+
|
|
175
|
+
Two more things worth knowing about servers of this shape:
|
|
176
|
+
|
|
177
|
+
- **A registry listing can be large.** TikTok's `tool_list` returns ~57 KB. Under `spill` that becomes a preview
|
|
178
|
+
plus a `fetch_blob` handle — useless, because choosing a tool is exactly what the full listing is for. Raise the
|
|
179
|
+
budget for that one tool: `override: (tool, info) => info.name === "tool_list" ? { ...tool, resultPolicy: { maxTokens: 100_000, overflow: "spill" } } : tool`.
|
|
180
|
+
- **Upstream business errors may not set `isError`.** TikTok returns `{"code": 40001, "message": "..."}` as a
|
|
181
|
+
*successful* MCP result; only MCP-level failures (unknown tool, transport error) are `isError`. Anything keying
|
|
182
|
+
off `isError` — retries, your own policies — will not see those. The model reads the code from the JSON.
|
|
183
|
+
|
|
105
184
|
## Verified upstream behaviour
|
|
106
185
|
|
|
107
186
|
Requests whose history contains `tool_use` / `tool_result` for a tool that is no longer in the tool table were
|
|
108
187
|
accepted by DeepSeek's Anthropic-protocol endpoint and by the OpenAI Responses protocol (both with another tool
|
|
109
188
|
present and with an empty tool table); the model answered and correctly listed only its current tools. So
|
|
110
|
-
removing a tool does not require a new session on those upstreams. Anthropic's own endpoint
|
|
111
|
-
|
|
112
|
-
|
|
189
|
+
removing a tool does not require a new session on those upstreams. Anthropic's own endpoint, reached through
|
|
190
|
+
Cloudflare AI Gateway's passthrough path, accepted both variants as well (2026-09-15, `spikes/l1-deferred-tools` P8);
|
|
191
|
+
an aggregator's Claude endpoint returned a truncated stream, a known quirk of that proxy rather than a protocol
|
|
192
|
+
rejection. Script: `spikes/mcp-removed-tool-history`.
|
|
113
193
|
|
|
114
194
|
## Options
|
|
115
195
|
|
|
116
196
|
| Option | Default | Meaning |
|
|
117
197
|
| --- | --- | --- |
|
|
118
|
-
| `transport` | — | `httpTransport({ url, headers?, fetch?, requestInit? })` or `stdioTransport({ command, args?, env?, cwd?, stderr? })` |
|
|
198
|
+
| `transport` | — | `httpTransport({ url, headers?, auth?, fetch?, requestInit? })` or `stdioTransport({ command, args?, env?, cwd?, stderr? })` |
|
|
119
199
|
| `prefix` | none | Prepended to tool names shown to the model; the server still sees the original name |
|
|
120
200
|
| `callTimeoutMs` | 60 000 | Per `tools/call` timeout; expiry becomes an error result |
|
|
121
201
|
| `optional` | `false` | On `tools/list` failure at run start: throw (default) or contribute no tools and warn once |
|
|
@@ -126,8 +206,8 @@ than a protocol rejection. Script: `spikes/mcp-removed-tool-history`.
|
|
|
126
206
|
Tool names that the model APIs reject (`^[A-Za-z0-9_-]{1,64}$`) are rewritten (`files.read` → `files_read`) with a
|
|
127
207
|
one-time warning; the original name is used on the wire.
|
|
128
208
|
|
|
129
|
-
Not
|
|
130
|
-
`
|
|
209
|
+
Not supported: sampling, elicitation, resources, prompts, MCP Apps, and driving an OAuth authorization
|
|
210
|
+
prompt (obtain tokens in your application and hand them over through `auth`).
|
|
131
211
|
|
|
132
212
|
## License
|
|
133
213
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import { M as
|
|
2
|
-
export {
|
|
1
|
+
import { M as McpAuth, a as McpTransport, b as McpToolsOptions, c as McpToolsSocket, d as McpToolAnnotations, e as McpToolInfo } from './types-F_1Kfslp.js';
|
|
2
|
+
export { f as McpToolsError } from './types-F_1Kfslp.js';
|
|
3
3
|
import { Tool, ContentPart } from '@reinsjs/core';
|
|
4
4
|
|
|
5
5
|
interface HttpTransportOptions {
|
|
6
6
|
url: string | URL;
|
|
7
|
-
/**
|
|
7
|
+
/** 每个请求附带的头(固定令牌等)。会过期的令牌用 `auth`,别写死在这里 */
|
|
8
8
|
headers?: Record<string, string>;
|
|
9
|
+
/**
|
|
10
|
+
* 每个请求的 bearer 凭证来源:`token()` 请求前现取、401 走 `onUnauthorized()` 刷新后自动重试一次。
|
|
11
|
+
* 与 `headers.Authorization` 互斥(两者都给在构造期即拒绝:静默让一个盖掉另一个,排查时看不出来)
|
|
12
|
+
*/
|
|
13
|
+
auth?: McpAuth;
|
|
9
14
|
/** 自定义 fetch(代理、测试里直连内存服务器) */
|
|
10
15
|
fetch?: typeof fetch;
|
|
11
16
|
/** 其余 RequestInit 字段(headers 之外) */
|
|
@@ -59,4 +64,4 @@ interface McpCallResult {
|
|
|
59
64
|
*/
|
|
60
65
|
declare function toContentParts(result: McpCallResult): ContentPart[];
|
|
61
66
|
|
|
62
|
-
export { DEFAULT_CALL_TIMEOUT_MS, type HttpTransportOptions, MODEL_TOOL_NAME_RE, McpToolAnnotations, McpToolInfo, McpToolsOptions, McpToolsSocket, McpTransport, REINS_MCP_CLIENT_INFO, httpTransport, mcpTools, modelToolName, riskOf, toContentParts, toToolInfo };
|
|
67
|
+
export { DEFAULT_CALL_TIMEOUT_MS, type HttpTransportOptions, MODEL_TOOL_NAME_RE, McpAuth, McpToolAnnotations, McpToolInfo, McpToolsOptions, McpToolsSocket, McpTransport, REINS_MCP_CLIENT_INFO, httpTransport, mcpTools, modelToolName, riskOf, toContentParts, toToolInfo };
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
// src/http.ts
|
|
2
2
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
|
|
3
|
+
function hasAuthorizationHeader(headers) {
|
|
4
|
+
if (!headers) return false;
|
|
5
|
+
return Object.keys(headers).some((k) => k.toLowerCase() === "authorization");
|
|
6
|
+
}
|
|
3
7
|
function httpTransport(options) {
|
|
8
|
+
if (options.auth && hasAuthorizationHeader(options.headers)) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
"httpTransport: auth and an Authorization header cannot be used together; keep the token in auth.token() so it is fetched fresh for every request"
|
|
11
|
+
);
|
|
12
|
+
}
|
|
4
13
|
const url = options.url instanceof URL ? options.url : new URL(options.url);
|
|
5
14
|
const label = `${url.origin}${url.pathname}`;
|
|
15
|
+
const auth = options.auth;
|
|
16
|
+
const authProvider = auth ? {
|
|
17
|
+
token: async () => await auth.token(),
|
|
18
|
+
...auth.onUnauthorized ? { onUnauthorized: async () => void await auth.onUnauthorized?.() } : {}
|
|
19
|
+
} : void 0;
|
|
6
20
|
return {
|
|
7
21
|
kind: "http",
|
|
8
22
|
label,
|
|
9
23
|
create: () => new StreamableHTTPClientTransport(url, {
|
|
10
24
|
requestInit: { ...options.requestInit, ...options.headers ? { headers: options.headers } : {} },
|
|
11
|
-
...options.fetch ? { fetch: options.fetch } : {}
|
|
25
|
+
...options.fetch ? { fetch: options.fetch } : {},
|
|
26
|
+
...authProvider ? { authProvider } : {}
|
|
12
27
|
})
|
|
13
28
|
};
|
|
14
29
|
}
|
|
@@ -33,11 +48,12 @@ function riskOf(annotations) {
|
|
|
33
48
|
return "medium";
|
|
34
49
|
}
|
|
35
50
|
function toToolInfo(raw) {
|
|
36
|
-
if (typeof raw !== "object" || raw === null) throw new TypeError("MCP
|
|
51
|
+
if (typeof raw !== "object" || raw === null) throw new TypeError("MCP tool declaration is not an object");
|
|
37
52
|
const t = raw;
|
|
38
|
-
if (typeof t.name !== "string" || t.name.length === 0)
|
|
53
|
+
if (typeof t.name !== "string" || t.name.length === 0)
|
|
54
|
+
throw new TypeError("MCP tool declaration has no name");
|
|
39
55
|
if (typeof t.inputSchema !== "object" || t.inputSchema === null)
|
|
40
|
-
throw new TypeError(`MCP
|
|
56
|
+
throw new TypeError(`MCP tool ${t.name} has no inputSchema`);
|
|
41
57
|
const info = { name: t.name, inputSchema: t.inputSchema };
|
|
42
58
|
if (typeof t.title === "string") info.title = t.title;
|
|
43
59
|
if (typeof t.description === "string") info.description = t.description;
|
|
@@ -144,7 +160,7 @@ var McpConnection = class {
|
|
|
144
160
|
await client.connect(this.transport.create());
|
|
145
161
|
} catch (err) {
|
|
146
162
|
throw new McpToolsError(
|
|
147
|
-
`MCP
|
|
163
|
+
`Failed to connect to MCP server (${this.transport.label}): ${messageOf(err)}`,
|
|
148
164
|
this.transport.label,
|
|
149
165
|
{ cause: err }
|
|
150
166
|
);
|
|
@@ -169,7 +185,7 @@ var McpConnection = class {
|
|
|
169
185
|
result = await client.listTools({}, { cacheMode: "bypass", ...signal ? { signal } : {} });
|
|
170
186
|
} catch (err) {
|
|
171
187
|
throw new McpToolsError(
|
|
172
|
-
`MCP tools/list
|
|
188
|
+
`MCP tools/list failed (${this.transport.label}): ${messageOf(err)}`,
|
|
173
189
|
this.transport.label,
|
|
174
190
|
{ cause: err }
|
|
175
191
|
);
|
|
@@ -214,7 +230,8 @@ function mcpTools(options) {
|
|
|
214
230
|
const warn = options.warn ?? ((m) => console.warn(m));
|
|
215
231
|
const prefix = options.prefix ?? "";
|
|
216
232
|
const timeoutMs = options.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS;
|
|
217
|
-
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
|
|
233
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
|
|
234
|
+
throw new RangeError("callTimeoutMs must be a positive number");
|
|
218
235
|
const conn = new McpConnection(transport, { clientInfo: options.clientInfo ?? REINS_MCP_CLIENT_INFO });
|
|
219
236
|
const warned = /* @__PURE__ */ new Set();
|
|
220
237
|
const warnOnce = (key, message) => {
|
|
@@ -225,7 +242,10 @@ function mcpTools(options) {
|
|
|
225
242
|
const toTool = (info) => {
|
|
226
243
|
const name = modelToolName(info.name, prefix);
|
|
227
244
|
if (name !== `${prefix}${info.name}`)
|
|
228
|
-
warnOnce(
|
|
245
|
+
warnOnce(
|
|
246
|
+
`name:${info.name}`,
|
|
247
|
+
`MCP tool name ${info.name} does not meet the model-facing requirements; the name shown to the model is rewritten to ${name}`
|
|
248
|
+
);
|
|
229
249
|
const risk = riskOf(info.annotations);
|
|
230
250
|
return defineTool({
|
|
231
251
|
name,
|
|
@@ -252,7 +272,10 @@ function mcpTools(options) {
|
|
|
252
272
|
infos = await conn.listTools();
|
|
253
273
|
} catch (err) {
|
|
254
274
|
if (!options.optional) throw err;
|
|
255
|
-
warnOnce(
|
|
275
|
+
warnOnce(
|
|
276
|
+
"list",
|
|
277
|
+
`MCP server ${transport.label} is unavailable, so this run carries none of its tools: ${messageOf2(err)}`
|
|
278
|
+
);
|
|
256
279
|
return [];
|
|
257
280
|
}
|
|
258
281
|
const tools = [];
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/http.ts","../src/mcp-tools.ts","../src/connection.ts","../src/translate.ts","../src/types.ts"],"sourcesContent":["/**\n * Streamable HTTP 传输配方(主入口,纯 Web 标准:只用 fetch / URL / Headers)。\n * 鉴权最简单的做法是 `headers: { Authorization: \"Bearer …\" }`;OAuth 流程 0.1 不封装,宿主可自带 `fetch` 包一层。\n */\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/client\"\nimport type { McpTransport } from \"./types.js\"\n\nexport interface HttpTransportOptions {\n url: string | URL\n /** 每个请求附带的头(鉴权等) */\n headers?: Record<string, string>\n /** 自定义 fetch(代理、测试里直连内存服务器) */\n fetch?: typeof fetch\n /** 其余 RequestInit 字段(headers 之外) */\n requestInit?: Omit<RequestInit, \"headers\">\n}\n\nexport function httpTransport(options: HttpTransportOptions): McpTransport {\n const url = options.url instanceof URL ? options.url : new URL(options.url)\n // 标签只留主机与路径:查询串里常见 token,不进日志\n const label = `${url.origin}${url.pathname}`\n return {\n kind: \"http\",\n label,\n create: () =>\n new StreamableHTTPClientTransport(url, {\n requestInit: { ...options.requestInit, ...(options.headers ? { headers: options.headers } : {}) },\n ...(options.fetch ? { fetch: options.fetch } : {}),\n }),\n }\n}\n","/**\n * `mcpTools(options): Socket` —— 把一台 MCP 服务器接成 reins 的一个 Socket(技术方案 §10,任务 P1)。\n *\n * 形状刻意最小:只用 Socket 的静态贡献 `tools`(异步函数),一个钩子都不挂。\n * - run 起步 `tools/list` 一次 → 每个 MCP 工具翻成 reins `Tool`,整个 run 工具表不变(§9.1 约束 3 / PRD\"不做动态注册\");\n * 服务器中途发 `listChanged` 只影响下一次 run —— 下一次起步再 list 一遍自然拿到新表,循环还会把增删告诉模型(tools_bound)。\n * - `execute` = `tools/call`;结果 content 翻成 ContentPart、`isError` 直通;抛错(未知工具、断连、超时)由循环记成\n * `tool_result(isError)`,循环不崩。\n * - annotations → `risk` 与 `needsApproval` 缺省(readOnly → low;destructive → high + 要审批;其余 medium)。\n * spill / approval / budget 在 afterTool / beforeTool 上自动生效,MCP 服务器不需要知道 reins(P3)。\n */\nimport { defineTool, REINS_VERSION, type Tool } from \"@reinsjs/core\"\nimport { McpConnection } from \"./connection.js\"\nimport { modelToolName, riskOf, toContentParts } from \"./translate.js\"\nimport type { McpToolInfo, McpToolsOptions, McpToolsSocket } from \"./types.js\"\n\nexport const DEFAULT_CALL_TIMEOUT_MS = 60_000\n/** MCP initialize 握手里报给服务器的客户端身份;版本跟随 core,不另维护一份 */\nexport const REINS_MCP_CLIENT_INFO = { name: \"reins\", version: REINS_VERSION }\n\nexport function mcpTools(options: McpToolsOptions): McpToolsSocket {\n const { transport } = options\n const warn = options.warn ?? ((m: string) => console.warn(m))\n const prefix = options.prefix ?? \"\"\n const timeoutMs = options.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS\n if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new RangeError(\"callTimeoutMs 必须是正数\")\n const conn = new McpConnection(transport, { clientInfo: options.clientInfo ?? REINS_MCP_CLIENT_INFO })\n const warned = new Set<string>()\n const warnOnce = (key: string, message: string) => {\n if (warned.has(key)) return\n warned.add(key)\n warn(message)\n }\n\n /** 一个 MCP 工具 → reins Tool。名字给模型看的可能被改写,调用时用原名 */\n const toTool = (info: McpToolInfo): Tool => {\n const name = modelToolName(info.name, prefix)\n if (name !== `${prefix}${info.name}`)\n warnOnce(`name:${info.name}`, `MCP 工具名 ${info.name} 不符合模型侧要求,给模型看的名字改写为 ${name}`)\n const risk = riskOf(info.annotations)\n return defineTool<unknown>({\n name,\n description: info.description ?? info.title ?? info.name,\n inputSchema: info.inputSchema,\n risk,\n // 明说破坏性的工具缺省要人点头;循环内置兜底会在没有 Socket 做主时直接转审批\n ...(risk === \"high\" ? { needsApproval: true } : {}),\n async execute(args, ctx) {\n const result = await conn.callTool(info.name, args, {\n timeoutMs,\n ...(ctx.signal ? { signal: ctx.signal } : {}),\n })\n return { content: toContentParts(result), isError: result.isError === true }\n },\n })\n }\n\n return {\n name: `mcp:${transport.label}`,\n transport,\n tools: async () => {\n let infos: McpToolInfo[]\n try {\n infos = await conn.listTools()\n } catch (err) {\n if (!options.optional) throw err\n warnOnce(\"list\", `MCP 服务器 ${transport.label} 不可用,本次 run 不带它的工具:${messageOf(err)}`)\n return []\n }\n const tools: Tool[] = []\n for (const info of infos) {\n const tool = toTool(info)\n const overridden = options.override?.(tool, info)\n if (overridden === false) continue\n tools.push(overridden ?? tool)\n }\n return tools\n },\n close: () => conn.close(),\n }\n}\n\nfunction messageOf(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n","/**\n * 一条 MCP 连接的生命周期:懒建、跨 run 复用、断了下次需要时按配方重建一次、宿主可关。\n *\n * 为什么不在这里做重连策略(退避、次数):库里的连接是给\"多次 run 复用\"的,一次 `tools/call` 失败就让模型\n * 看到 isError 自己决定要不要再试(宪法一);跨 run 的\"重建一次\"只是把\"上次断了\"这个状态清掉,\n * 不是重试。真要保活、要退避的宿主自己包一层 transport。\n */\nimport { Client, type Transport } from \"@modelcontextprotocol/client\"\nimport type { McpCallResult } from \"./translate.js\"\nimport { toToolInfo } from \"./translate.js\"\nimport { type McpToolInfo, McpToolsError, type McpTransport } from \"./types.js\"\n\nexport interface McpConnectionOptions {\n clientInfo: { name: string; version: string }\n}\n\nexport class McpConnection {\n private client: Client | undefined\n private connecting: Promise<Client> | undefined\n\n constructor(\n readonly transport: McpTransport,\n private readonly options: McpConnectionOptions,\n ) {}\n\n /** 当前是否持有一条活连接(测试与排错用) */\n get connected(): boolean {\n return this.client !== undefined\n }\n\n /** 拿到已连接的 Client:没有就建;正在建就等同一次;上次断了(onclose 已清掉)就重建 */\n private async ensure(): Promise<Client> {\n if (this.client) return this.client\n if (this.connecting) return this.connecting\n this.connecting = (async () => {\n const client = new Client(this.options.clientInfo)\n try {\n await client.connect(this.transport.create() as Transport)\n } catch (err) {\n throw new McpToolsError(\n `MCP 服务器连接失败(${this.transport.label}):${messageOf(err)}`,\n this.transport.label,\n { cause: err },\n )\n }\n // 传输断开(服务器退出、网络断):清掉引用,下一次 ensure 重建。不在这里自动重连\n client.onclose = () => {\n if (this.client === client) this.client = undefined\n }\n this.client = client\n return client\n })()\n try {\n return await this.connecting\n } finally {\n this.connecting = undefined\n }\n }\n\n /** `tools/list`(SDK 会自动翻完全部分页)。绕过 SDK 的列表缓存:每次 run 起步要的就是服务器此刻的表 */\n async listTools(signal?: AbortSignal): Promise<McpToolInfo[]> {\n const client = await this.ensure()\n let result: { tools: unknown[] }\n try {\n result = await client.listTools({}, { cacheMode: \"bypass\", ...(signal ? { signal } : {}) })\n } catch (err) {\n throw new McpToolsError(\n `MCP tools/list 失败(${this.transport.label}):${messageOf(err)}`,\n this.transport.label,\n { cause: err },\n )\n }\n return result.tools.map(toToolInfo)\n }\n\n /** `tools/call`。抛错(未知工具、断连、超时)交给调用方翻成 isError 结果 */\n async callTool(\n name: string,\n args: unknown,\n opts: { signal?: AbortSignal; timeoutMs: number },\n ): Promise<McpCallResult> {\n const client = await this.ensure()\n const result = await client.callTool(\n { name, arguments: (args ?? {}) as Record<string, unknown> },\n { timeout: opts.timeoutMs, ...(opts.signal ? { signal: opts.signal } : {}) },\n )\n return result as McpCallResult\n }\n\n /**\n * 关掉当前连接(宿主热轮换 Socket、进程收尾)。\n * 正在建连时调用:等这次建连有结果再关——否则 `ensure()` 完成后会把新 Client 挂回 `this.client`,\n * 连接泄漏、`onclose` 回调悬挂(2026-09-10 审查修)。建连失败本来就没有连接,吞掉那个错误。\n */\n async close(): Promise<void> {\n if (this.connecting) {\n try {\n await this.connecting\n } catch {\n // 建连失败:没有连接可关\n }\n }\n const client = this.client\n this.client = undefined\n if (client) await client.close()\n }\n}\n\nfunction messageOf(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n","/**\n * MCP ⇄ reins 的纯函数翻译:工具声明 → `Tool` 的静态部分,`tools/call` 结果 → `ContentPart[]`。\n * 不碰网络、不碰事件,单测直接喂 JSON。\n *\n * 有损的地方都在这里明说(P7):reins 的 ContentPart 只有文本与图片,MCP 的 audio / 二进制 resource\n * 翻成一行说明文字告诉模型\"有这么个东西但看不到\",不静默丢。\n */\nimport type { ContentPart, Tool } from \"@reinsjs/core\"\nimport type { McpToolAnnotations, McpToolInfo } from \"./types.js\"\n\n/** Anthropic / OpenAI 对工具名的共同要求;MCP 没有这个限制,不合规的名字要改写,否则请求 400 */\nexport const MODEL_TOOL_NAME_RE = /^[A-Za-z0-9_-]{1,64}$/\n\n/** 给模型看的名字:加前缀、把不合规字符换成 `_`、截到 64。返回值与原名不同则调用方应告警一次 */\nexport function modelToolName(mcpName: string, prefix = \"\"): string {\n const raw = `${prefix}${mcpName}`\n if (MODEL_TOOL_NAME_RE.test(raw)) return raw\n const cleaned = raw.replace(/[^A-Za-z0-9_-]/g, \"_\").slice(0, 64)\n return cleaned.length > 0 ? cleaned : \"_\"\n}\n\n/**\n * 注解 → 风险档。spec 说注解只是提示、不可信服务器的注解不能当权限依据,所以这里只定**缺省值**:\n * 明说只读 → low;明说破坏性 → high(并缺省要审批);其余(含完全没注解)→ medium,交给 approval 模块的策略。\n * 注意 spec 里 destructiveHint 缺省为 true,我们刻意不把\"没写\"当\"破坏性\"——否则每个 MCP 工具都要审批,\n * 宿主要那么严可以用 `override` 自己定。\n */\nexport function riskOf(annotations: McpToolAnnotations | undefined): NonNullable<Tool[\"risk\"]> {\n if (annotations?.destructiveHint === true) return \"high\"\n if (annotations?.readOnlyHint === true) return \"low\"\n return \"medium\"\n}\n\n/** `tools/list` 里的一项 → 纯数据 McpToolInfo;形状不对就抛(服务器声明不合法,宁可起步失败) */\nexport function toToolInfo(raw: unknown): McpToolInfo {\n if (typeof raw !== \"object\" || raw === null) throw new TypeError(\"MCP 工具声明不是对象\")\n const t = raw as Record<string, unknown>\n if (typeof t.name !== \"string\" || t.name.length === 0) throw new TypeError(\"MCP 工具声明缺少 name\")\n if (typeof t.inputSchema !== \"object\" || t.inputSchema === null)\n throw new TypeError(`MCP 工具 ${t.name} 缺少 inputSchema`)\n const info: McpToolInfo = { name: t.name, inputSchema: t.inputSchema as Record<string, unknown> }\n if (typeof t.title === \"string\") info.title = t.title\n if (typeof t.description === \"string\") info.description = t.description\n if (typeof t.outputSchema === \"object\" && t.outputSchema !== null)\n info.outputSchema = t.outputSchema as Record<string, unknown>\n if (typeof t.annotations === \"object\" && t.annotations !== null)\n info.annotations = t.annotations as McpToolAnnotations\n return info\n}\n\n/** base64 文本对应的字节数(去掉填充) */\nfunction base64Bytes(b64: string): number {\n const padding = b64.endsWith(\"==\") ? 2 : b64.endsWith(\"=\") ? 1 : 0\n return Math.max(0, Math.floor((b64.length * 3) / 4) - padding)\n}\n\n/** `tools/call` 结果的形状(只取我们用到的字段;SDK 类型不出本包) */\nexport interface McpCallResult {\n content?: unknown\n structuredContent?: unknown\n isError?: unknown\n}\n\n/**\n * MCP 内容块 → reins 内容片段。\n * - text → 文本;image → 图片(mime + base64)\n * - audio → 一行说明(模型看不到音频)\n * - resource_link → 一行 \"Resource link: uri (name) [mime]\"\n * - resource(内嵌):文本资源 → 带 uri 头的文本;图片二进制 → 图片;其他二进制 → 一行说明\n * - 不认识的块 → JSON 文本(不静默丢)\n * - content 为空而有 structuredContent → 其 JSON;都没有 → 空文本(模型总得有个结果块)\n */\nexport function toContentParts(result: McpCallResult): ContentPart[] {\n const blocks = Array.isArray(result.content) ? result.content : []\n const parts: ContentPart[] = []\n for (const raw of blocks) {\n const b = (typeof raw === \"object\" && raw !== null ? raw : {}) as Record<string, unknown>\n switch (b.type) {\n case \"text\":\n parts.push({ type: \"text\", text: typeof b.text === \"string\" ? b.text : \"\" })\n break\n case \"image\":\n if (typeof b.data === \"string\" && typeof b.mimeType === \"string\")\n parts.push({ type: \"image\", mime: b.mimeType, data: b.data })\n else parts.push({ type: \"text\", text: \"[image block without data/mimeType]\" })\n break\n case \"audio\": {\n const bytes = typeof b.data === \"string\" ? base64Bytes(b.data) : 0\n parts.push({\n type: \"text\",\n text: `[audio ${String(b.mimeType ?? \"unknown type\")}, ${bytes} bytes; audio cannot be shown to the model]`,\n })\n break\n }\n case \"resource_link\": {\n const name = typeof b.name === \"string\" ? ` (${b.name})` : \"\"\n const mime = typeof b.mimeType === \"string\" ? ` [${b.mimeType}]` : \"\"\n const desc = typeof b.description === \"string\" ? ` — ${b.description}` : \"\"\n parts.push({ type: \"text\", text: `Resource link: ${String(b.uri)}${name}${mime}${desc}` })\n break\n }\n case \"resource\": {\n const r = (typeof b.resource === \"object\" && b.resource !== null ? b.resource : {}) as Record<\n string,\n unknown\n >\n const uri = String(r.uri ?? \"\")\n const mime = typeof r.mimeType === \"string\" ? r.mimeType : undefined\n if (typeof r.text === \"string\") {\n parts.push({ type: \"text\", text: `[resource ${uri}${mime ? ` (${mime})` : \"\"}]\\n${r.text}` })\n } else if (typeof r.blob === \"string\" && mime?.startsWith(\"image/\")) {\n parts.push({ type: \"image\", mime, data: r.blob })\n } else {\n const bytes = typeof r.blob === \"string\" ? base64Bytes(r.blob) : 0\n parts.push({\n type: \"text\",\n text: `[binary resource ${uri}${mime ? ` (${mime})` : \"\"}, ${bytes} bytes; not shown to the model]`,\n })\n }\n break\n }\n default:\n parts.push({ type: \"text\", text: JSON.stringify(raw) ?? String(raw) })\n }\n }\n if (parts.length === 0) {\n if (result.structuredContent !== undefined && result.structuredContent !== null)\n parts.push({ type: \"text\", text: JSON.stringify(result.structuredContent) ?? \"\" })\n else parts.push({ type: \"text\", text: \"\" })\n }\n return parts\n}\n","/**\n * @reinsjs/tools-mcp 的公开类型。MCP SDK 的类型一律不出本包(同降级层对 pi-ai 的约束):\n * 对外只有 reins 自己的 `Tool` / `Socket`,加上这里几个纯数据形状。\n */\nimport type { Socket, Tool } from \"@reinsjs/core\"\n\n/** MCP 工具注解(spec 里全部是\"提示\",只用来给 risk / needsApproval 定缺省值,不当权限判定) */\nexport interface McpToolAnnotations {\n title?: string\n readOnlyHint?: boolean\n destructiveHint?: boolean\n idempotentHint?: boolean\n openWorldHint?: boolean\n}\n\n/** `tools/list` 返回的一个工具,原样搬过来的纯数据;`override` 钩子拿它做逐工具改写的依据 */\nexport interface McpToolInfo {\n name: string\n title?: string\n description?: string\n inputSchema: Record<string, unknown>\n outputSchema?: Record<string, unknown>\n annotations?: McpToolAnnotations\n}\n\n/**\n * 一条 MCP 传输的\"配方\":怎么建出底层连接。主入口只有 `httpTransport`(Streamable HTTP,纯 Web 标准),\n * `@reinsjs/tools-mcp/node` 才有 `stdioTransport`(起子进程)。连接本身由 `mcpTools()` 懒建、跨 run 复用,\n * 断了在下一次需要时按这个配方重建一次 —— 所以配方必须可重复调用。\n */\nexport interface McpTransport {\n readonly kind: \"http\" | \"stdio\" | \"custom\"\n /** 给 Socket.name、告警与报错用的可读标签(不含密钥) */\n readonly label: string\n /**\n * 建一条新的底层传输。返回值是 MCP SDK 的 Transport,对外按 unknown 处理(SDK 类型不出本包);\n * 自定义传输(如测试用的内存传输)也从这里进\n */\n create(): unknown\n}\n\nexport interface McpToolsOptions {\n transport: McpTransport\n /**\n * 给模型看的工具名前缀(如 `\"gh_\"`),多台服务器同名工具靠它区分;缺省无。\n * 调用时按原名发给服务器。宿主工具与 MCP 工具同名时以宿主为准(core 静态贡献的既有规则)\n */\n prefix?: string\n /** 单次 `tools/call` 超时毫秒,缺省 60_000;到点抛错 → 模型看到 isError 结果,循环不崩 */\n callTimeoutMs?: number\n /**\n * run 起步 `tools/list` 失败时怎么办。缺省 false:抛错,run 在写任何日志之前失败(fail-closed,宿主一定知道)。\n * true:本次 run 不贡献工具、告警一次,模型会从工具变化说明里看到这些工具被移除了\n */\n optional?: boolean\n /**\n * 逐工具改写:调整 needsApproval / risk / resultPolicy / description 等;返回 `false` 即不把这个工具给模型。\n * 入参 `tool` 是按缺省规则翻好的 reins Tool,`info` 是服务器原样的声明\n */\n override?(tool: Tool, info: McpToolInfo): Tool | false | undefined\n /** MCP 握手时的客户端自述,缺省 `{ name: \"reins\", version: <本包版本> }` */\n clientInfo?: { name: string; version: string }\n /** 告警出口(optional 降级、工具名被改写),缺省 console.warn */\n warn?: (message: string) => void\n}\n\nexport interface McpToolsSocket extends Socket {\n readonly transport: McpTransport\n /** 关掉底层连接。进程退出、或宿主按请求重建配置时换掉旧的之前调;关掉后再跑 run 会按配方重建 */\n close(): Promise<void>\n}\n\n/** 本包抛出的错误:连接建不起来、`tools/list` 失败、服务器声明不合法 */\nexport class McpToolsError extends Error {\n constructor(\n message: string,\n readonly transport: string,\n options?: { cause?: unknown },\n ) {\n super(message, options)\n this.name = \"McpToolsError\"\n }\n}\n"],"mappings":"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","names":["messageOf"]}
|
|
1
|
+
{"version":3,"sources":["../src/http.ts","../src/mcp-tools.ts","../src/connection.ts","../src/translate.ts","../src/types.ts"],"sourcesContent":["/**\n * Streamable HTTP 传输配方(主入口,纯 Web 标准:只用 fetch / URL / Headers)。\n *\n * 固定令牌用 `headers: { Authorization: \"Bearer …\" }` 就够。会过期的令牌(OAuth)必须用 `auth`:\n * headers 在构造时就定死了,而连接是懒建、跨 run 复用、断了按同一配方重建的——令牌一过期,重建也是拿旧的。\n * `auth.token()` 每个请求前现取,401 时 `onUnauthorized()` 刷新后自动重试一次(都由 MCP SDK 的传输层做)。\n */\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/client\"\nimport type { McpAuth, McpTransport } from \"./types.js\"\n\nexport interface HttpTransportOptions {\n url: string | URL\n /** 每个请求附带的头(固定令牌等)。会过期的令牌用 `auth`,别写死在这里 */\n headers?: Record<string, string>\n /**\n * 每个请求的 bearer 凭证来源:`token()` 请求前现取、401 走 `onUnauthorized()` 刷新后自动重试一次。\n * 与 `headers.Authorization` 互斥(两者都给在构造期即拒绝:静默让一个盖掉另一个,排查时看不出来)\n */\n auth?: McpAuth\n /** 自定义 fetch(代理、测试里直连内存服务器) */\n fetch?: typeof fetch\n /** 其余 RequestInit 字段(headers 之外) */\n requestInit?: Omit<RequestInit, \"headers\">\n}\n\n/** headers 里的 Authorization(大小写不敏感:HTTP 头名不区分大小写,宿主写 authorization 也算) */\nfunction hasAuthorizationHeader(headers: Record<string, string> | undefined): boolean {\n if (!headers) return false\n return Object.keys(headers).some((k) => k.toLowerCase() === \"authorization\")\n}\n\nexport function httpTransport(options: HttpTransportOptions): McpTransport {\n if (options.auth && hasAuthorizationHeader(options.headers)) {\n throw new Error(\n \"httpTransport: auth and an Authorization header cannot be used together; keep the token in auth.token() so it is fetched fresh for every request\",\n )\n }\n const url = options.url instanceof URL ? options.url : new URL(options.url)\n // 标签只留主机与路径:查询串里常见 token,不进日志\n const label = `${url.origin}${url.pathname}`\n // 包一层而不是直接把 McpAuth 交给 SDK:我们的签名收 MaybePromise,宿主同步返回令牌也行\n const auth = options.auth\n const authProvider = auth\n ? {\n token: async () => await auth.token(),\n ...(auth.onUnauthorized ? { onUnauthorized: async () => void (await auth.onUnauthorized?.()) } : {}),\n }\n : undefined\n return {\n kind: \"http\",\n label,\n create: () =>\n new StreamableHTTPClientTransport(url, {\n requestInit: { ...options.requestInit, ...(options.headers ? { headers: options.headers } : {}) },\n ...(options.fetch ? { fetch: options.fetch } : {}),\n ...(authProvider ? { authProvider } : {}),\n }),\n }\n}\n","/**\n * `mcpTools(options): Socket` —— 把一台 MCP 服务器接成 reins 的一个 Socket(技术方案 §10,任务 P1)。\n *\n * 形状刻意最小:只用 Socket 的静态贡献 `tools`(异步函数),一个钩子都不挂。\n * - run 起步 `tools/list` 一次 → 每个 MCP 工具翻成 reins `Tool`,整个 run 工具表不变(§9.1 约束 3 / PRD\"不做动态注册\");\n * 服务器中途发 `listChanged` 只影响下一次 run —— 下一次起步再 list 一遍自然拿到新表,循环还会把增删告诉模型(tools_bound)。\n * - `execute` = `tools/call`;结果 content 翻成 ContentPart、`isError` 直通;抛错(未知工具、断连、超时)由循环记成\n * `tool_result(isError)`,循环不崩。\n * - annotations → `risk` 与 `needsApproval` 缺省(readOnly → low;destructive → high + 要审批;其余 medium)。\n * spill / approval / budget 在 afterTool / beforeTool 上自动生效,MCP 服务器不需要知道 reins(P3)。\n */\nimport { defineTool, REINS_VERSION, type Tool } from \"@reinsjs/core\"\nimport { McpConnection } from \"./connection.js\"\nimport { modelToolName, riskOf, toContentParts } from \"./translate.js\"\nimport type { McpToolInfo, McpToolsOptions, McpToolsSocket } from \"./types.js\"\n\nexport const DEFAULT_CALL_TIMEOUT_MS = 60_000\n/** MCP initialize 握手里报给服务器的客户端身份;版本跟随 core,不另维护一份 */\nexport const REINS_MCP_CLIENT_INFO = { name: \"reins\", version: REINS_VERSION }\n\nexport function mcpTools(options: McpToolsOptions): McpToolsSocket {\n const { transport } = options\n const warn = options.warn ?? ((m: string) => console.warn(m))\n const prefix = options.prefix ?? \"\"\n const timeoutMs = options.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS\n if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)\n throw new RangeError(\"callTimeoutMs must be a positive number\")\n const conn = new McpConnection(transport, { clientInfo: options.clientInfo ?? REINS_MCP_CLIENT_INFO })\n const warned = new Set<string>()\n const warnOnce = (key: string, message: string) => {\n if (warned.has(key)) return\n warned.add(key)\n warn(message)\n }\n\n /** 一个 MCP 工具 → reins Tool。名字给模型看的可能被改写,调用时用原名 */\n const toTool = (info: McpToolInfo): Tool => {\n const name = modelToolName(info.name, prefix)\n if (name !== `${prefix}${info.name}`)\n warnOnce(\n `name:${info.name}`,\n `MCP tool name ${info.name} does not meet the model-facing requirements; the name shown to the model is rewritten to ${name}`,\n )\n const risk = riskOf(info.annotations)\n return defineTool<unknown>({\n name,\n description: info.description ?? info.title ?? info.name,\n inputSchema: info.inputSchema,\n risk,\n // 明说破坏性的工具缺省要人点头;循环内置兜底会在没有 Socket 做主时直接转审批\n ...(risk === \"high\" ? { needsApproval: true } : {}),\n async execute(args, ctx) {\n const result = await conn.callTool(info.name, args, {\n timeoutMs,\n ...(ctx.signal ? { signal: ctx.signal } : {}),\n })\n return { content: toContentParts(result), isError: result.isError === true }\n },\n })\n }\n\n return {\n name: `mcp:${transport.label}`,\n transport,\n tools: async () => {\n let infos: McpToolInfo[]\n try {\n infos = await conn.listTools()\n } catch (err) {\n if (!options.optional) throw err\n warnOnce(\n \"list\",\n `MCP server ${transport.label} is unavailable, so this run carries none of its tools: ${messageOf(err)}`,\n )\n return []\n }\n const tools: Tool[] = []\n for (const info of infos) {\n const tool = toTool(info)\n const overridden = options.override?.(tool, info)\n if (overridden === false) continue\n tools.push(overridden ?? tool)\n }\n return tools\n },\n close: () => conn.close(),\n }\n}\n\nfunction messageOf(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n","/**\n * 一条 MCP 连接的生命周期:懒建、跨 run 复用、断了下次需要时按配方重建一次、宿主可关。\n *\n * 为什么不在这里做重连策略(退避、次数):库里的连接是给\"多次 run 复用\"的,一次 `tools/call` 失败就让模型\n * 看到 isError 自己决定要不要再试(宪法一);跨 run 的\"重建一次\"只是把\"上次断了\"这个状态清掉,\n * 不是重试。真要保活、要退避的宿主自己包一层 transport。\n */\nimport { Client, type Transport } from \"@modelcontextprotocol/client\"\nimport type { McpCallResult } from \"./translate.js\"\nimport { toToolInfo } from \"./translate.js\"\nimport { type McpToolInfo, McpToolsError, type McpTransport } from \"./types.js\"\n\nexport interface McpConnectionOptions {\n clientInfo: { name: string; version: string }\n}\n\nexport class McpConnection {\n private client: Client | undefined\n private connecting: Promise<Client> | undefined\n\n constructor(\n readonly transport: McpTransport,\n private readonly options: McpConnectionOptions,\n ) {}\n\n /** 当前是否持有一条活连接(测试与排错用) */\n get connected(): boolean {\n return this.client !== undefined\n }\n\n /** 拿到已连接的 Client:没有就建;正在建就等同一次;上次断了(onclose 已清掉)就重建 */\n private async ensure(): Promise<Client> {\n if (this.client) return this.client\n if (this.connecting) return this.connecting\n this.connecting = (async () => {\n const client = new Client(this.options.clientInfo)\n try {\n await client.connect(this.transport.create() as Transport)\n } catch (err) {\n throw new McpToolsError(\n `Failed to connect to MCP server (${this.transport.label}): ${messageOf(err)}`,\n this.transport.label,\n { cause: err },\n )\n }\n // 传输断开(服务器退出、网络断):清掉引用,下一次 ensure 重建。不在这里自动重连\n client.onclose = () => {\n if (this.client === client) this.client = undefined\n }\n this.client = client\n return client\n })()\n try {\n return await this.connecting\n } finally {\n this.connecting = undefined\n }\n }\n\n /** `tools/list`(SDK 会自动翻完全部分页)。绕过 SDK 的列表缓存:每次 run 起步要的就是服务器此刻的表 */\n async listTools(signal?: AbortSignal): Promise<McpToolInfo[]> {\n const client = await this.ensure()\n let result: { tools: unknown[] }\n try {\n result = await client.listTools({}, { cacheMode: \"bypass\", ...(signal ? { signal } : {}) })\n } catch (err) {\n throw new McpToolsError(\n `MCP tools/list failed (${this.transport.label}): ${messageOf(err)}`,\n this.transport.label,\n { cause: err },\n )\n }\n return result.tools.map(toToolInfo)\n }\n\n /** `tools/call`。抛错(未知工具、断连、超时)交给调用方翻成 isError 结果 */\n async callTool(\n name: string,\n args: unknown,\n opts: { signal?: AbortSignal; timeoutMs: number },\n ): Promise<McpCallResult> {\n const client = await this.ensure()\n const result = await client.callTool(\n { name, arguments: (args ?? {}) as Record<string, unknown> },\n { timeout: opts.timeoutMs, ...(opts.signal ? { signal: opts.signal } : {}) },\n )\n return result as McpCallResult\n }\n\n /**\n * 关掉当前连接(宿主热轮换 Socket、进程收尾)。\n * 正在建连时调用:等这次建连有结果再关——否则 `ensure()` 完成后会把新 Client 挂回 `this.client`,\n * 连接泄漏、`onclose` 回调悬挂(2026-09-10 审查修)。建连失败本来就没有连接,吞掉那个错误。\n */\n async close(): Promise<void> {\n if (this.connecting) {\n try {\n await this.connecting\n } catch {\n // 建连失败:没有连接可关\n }\n }\n const client = this.client\n this.client = undefined\n if (client) await client.close()\n }\n}\n\nfunction messageOf(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n","/**\n * MCP ⇄ reins 的纯函数翻译:工具声明 → `Tool` 的静态部分,`tools/call` 结果 → `ContentPart[]`。\n * 不碰网络、不碰事件,单测直接喂 JSON。\n *\n * 有损的地方都在这里明说(P7):reins 的 ContentPart 只有文本与图片,MCP 的 audio / 二进制 resource\n * 翻成一行说明文字告诉模型\"有这么个东西但看不到\",不静默丢。\n */\nimport type { ContentPart, Tool } from \"@reinsjs/core\"\nimport type { McpToolAnnotations, McpToolInfo } from \"./types.js\"\n\n/** Anthropic / OpenAI 对工具名的共同要求;MCP 没有这个限制,不合规的名字要改写,否则请求 400 */\nexport const MODEL_TOOL_NAME_RE = /^[A-Za-z0-9_-]{1,64}$/\n\n/** 给模型看的名字:加前缀、把不合规字符换成 `_`、截到 64。返回值与原名不同则调用方应告警一次 */\nexport function modelToolName(mcpName: string, prefix = \"\"): string {\n const raw = `${prefix}${mcpName}`\n if (MODEL_TOOL_NAME_RE.test(raw)) return raw\n const cleaned = raw.replace(/[^A-Za-z0-9_-]/g, \"_\").slice(0, 64)\n return cleaned.length > 0 ? cleaned : \"_\"\n}\n\n/**\n * 注解 → 风险档。spec 说注解只是提示、不可信服务器的注解不能当权限依据,所以这里只定**缺省值**:\n * 明说只读 → low;明说破坏性 → high(并缺省要审批);其余(含完全没注解)→ medium,交给 approval 模块的策略。\n * 注意 spec 里 destructiveHint 缺省为 true,我们刻意不把\"没写\"当\"破坏性\"——否则每个 MCP 工具都要审批,\n * 宿主要那么严可以用 `override` 自己定。\n */\nexport function riskOf(annotations: McpToolAnnotations | undefined): NonNullable<Tool[\"risk\"]> {\n if (annotations?.destructiveHint === true) return \"high\"\n if (annotations?.readOnlyHint === true) return \"low\"\n return \"medium\"\n}\n\n/** `tools/list` 里的一项 → 纯数据 McpToolInfo;形状不对就抛(服务器声明不合法,宁可起步失败) */\nexport function toToolInfo(raw: unknown): McpToolInfo {\n if (typeof raw !== \"object\" || raw === null) throw new TypeError(\"MCP tool declaration is not an object\")\n const t = raw as Record<string, unknown>\n if (typeof t.name !== \"string\" || t.name.length === 0)\n throw new TypeError(\"MCP tool declaration has no name\")\n if (typeof t.inputSchema !== \"object\" || t.inputSchema === null)\n throw new TypeError(`MCP tool ${t.name} has no inputSchema`)\n const info: McpToolInfo = { name: t.name, inputSchema: t.inputSchema as Record<string, unknown> }\n if (typeof t.title === \"string\") info.title = t.title\n if (typeof t.description === \"string\") info.description = t.description\n if (typeof t.outputSchema === \"object\" && t.outputSchema !== null)\n info.outputSchema = t.outputSchema as Record<string, unknown>\n if (typeof t.annotations === \"object\" && t.annotations !== null)\n info.annotations = t.annotations as McpToolAnnotations\n return info\n}\n\n/** base64 文本对应的字节数(去掉填充) */\nfunction base64Bytes(b64: string): number {\n const padding = b64.endsWith(\"==\") ? 2 : b64.endsWith(\"=\") ? 1 : 0\n return Math.max(0, Math.floor((b64.length * 3) / 4) - padding)\n}\n\n/** `tools/call` 结果的形状(只取我们用到的字段;SDK 类型不出本包) */\nexport interface McpCallResult {\n content?: unknown\n structuredContent?: unknown\n isError?: unknown\n}\n\n/**\n * MCP 内容块 → reins 内容片段。\n * - text → 文本;image → 图片(mime + base64)\n * - audio → 一行说明(模型看不到音频)\n * - resource_link → 一行 \"Resource link: uri (name) [mime]\"\n * - resource(内嵌):文本资源 → 带 uri 头的文本;图片二进制 → 图片;其他二进制 → 一行说明\n * - 不认识的块 → JSON 文本(不静默丢)\n * - content 为空而有 structuredContent → 其 JSON;都没有 → 空文本(模型总得有个结果块)\n */\nexport function toContentParts(result: McpCallResult): ContentPart[] {\n const blocks = Array.isArray(result.content) ? result.content : []\n const parts: ContentPart[] = []\n for (const raw of blocks) {\n const b = (typeof raw === \"object\" && raw !== null ? raw : {}) as Record<string, unknown>\n switch (b.type) {\n case \"text\":\n parts.push({ type: \"text\", text: typeof b.text === \"string\" ? b.text : \"\" })\n break\n case \"image\":\n if (typeof b.data === \"string\" && typeof b.mimeType === \"string\")\n parts.push({ type: \"image\", mime: b.mimeType, data: b.data })\n else parts.push({ type: \"text\", text: \"[image block without data/mimeType]\" })\n break\n case \"audio\": {\n const bytes = typeof b.data === \"string\" ? base64Bytes(b.data) : 0\n parts.push({\n type: \"text\",\n text: `[audio ${String(b.mimeType ?? \"unknown type\")}, ${bytes} bytes; audio cannot be shown to the model]`,\n })\n break\n }\n case \"resource_link\": {\n const name = typeof b.name === \"string\" ? ` (${b.name})` : \"\"\n const mime = typeof b.mimeType === \"string\" ? ` [${b.mimeType}]` : \"\"\n const desc = typeof b.description === \"string\" ? ` — ${b.description}` : \"\"\n parts.push({ type: \"text\", text: `Resource link: ${String(b.uri)}${name}${mime}${desc}` })\n break\n }\n case \"resource\": {\n const r = (typeof b.resource === \"object\" && b.resource !== null ? b.resource : {}) as Record<\n string,\n unknown\n >\n const uri = String(r.uri ?? \"\")\n const mime = typeof r.mimeType === \"string\" ? r.mimeType : undefined\n if (typeof r.text === \"string\") {\n parts.push({ type: \"text\", text: `[resource ${uri}${mime ? ` (${mime})` : \"\"}]\\n${r.text}` })\n } else if (typeof r.blob === \"string\" && mime?.startsWith(\"image/\")) {\n parts.push({ type: \"image\", mime, data: r.blob })\n } else {\n const bytes = typeof r.blob === \"string\" ? base64Bytes(r.blob) : 0\n parts.push({\n type: \"text\",\n text: `[binary resource ${uri}${mime ? ` (${mime})` : \"\"}, ${bytes} bytes; not shown to the model]`,\n })\n }\n break\n }\n default:\n parts.push({ type: \"text\", text: JSON.stringify(raw) ?? String(raw) })\n }\n }\n if (parts.length === 0) {\n if (result.structuredContent !== undefined && result.structuredContent !== null)\n parts.push({ type: \"text\", text: JSON.stringify(result.structuredContent) ?? \"\" })\n else parts.push({ type: \"text\", text: \"\" })\n }\n return parts\n}\n","/**\n * @reinsjs/tools-mcp 的公开类型。MCP SDK 的类型一律不出本包(同降级层对 pi-ai 的约束):\n * 对外只有 reins 自己的 `Tool` / `Socket`,加上这里几个纯数据形状。\n */\nimport type { MaybePromise, Socket, Tool } from \"@reinsjs/core\"\n\n/**\n * 每个请求的 bearer 凭证来源(HTTP 传输)。刻意只有两个方法、且不引用 MCP SDK 的类型:\n * 形状与 SDK 的 `AuthProvider` 结构兼容,宿主不必 import SDK。\n *\n * **不收 SDK 的 `OAuthClientProvider`**:那套的核心是\"开浏览器让人点授权\",在服务端 agent 里跑不通——\n * 令牌该由宿主的 Web 应用先拿好、存起来,循环里只负责取用与刷新。完整 OAuth 流程仍不封装(0.1 立场不变),\n * 宿主要用可以在 `token` / `onUnauthorized` 里自己调 SDK 的 `refreshAuthorization` 等函数。\n */\nexport interface McpAuth {\n /** 每个请求发出前调用。返回 undefined 表示这次不带 Authorization 头 */\n token(): MaybePromise<string | undefined>\n /**\n * 服务器回 401 时调用,之后传输层**自动重试该请求一次**;重试再 401(或没给这个方法)就抛错。\n * 实现里该做的是刷新令牌——下一次 `token()` 要能返回可用的那个\n */\n onUnauthorized?(): MaybePromise<void>\n}\n\n/** MCP 工具注解(spec 里全部是\"提示\",只用来给 risk / needsApproval 定缺省值,不当权限判定) */\nexport interface McpToolAnnotations {\n title?: string\n readOnlyHint?: boolean\n destructiveHint?: boolean\n idempotentHint?: boolean\n openWorldHint?: boolean\n}\n\n/** `tools/list` 返回的一个工具,原样搬过来的纯数据;`override` 钩子拿它做逐工具改写的依据 */\nexport interface McpToolInfo {\n name: string\n title?: string\n description?: string\n inputSchema: Record<string, unknown>\n outputSchema?: Record<string, unknown>\n annotations?: McpToolAnnotations\n}\n\n/**\n * 一条 MCP 传输的\"配方\":怎么建出底层连接。主入口只有 `httpTransport`(Streamable HTTP,纯 Web 标准),\n * `@reinsjs/tools-mcp/node` 才有 `stdioTransport`(起子进程)。连接本身由 `mcpTools()` 懒建、跨 run 复用,\n * 断了在下一次需要时按这个配方重建一次 —— 所以配方必须可重复调用。\n */\nexport interface McpTransport {\n readonly kind: \"http\" | \"stdio\" | \"custom\"\n /** 给 Socket.name、告警与报错用的可读标签(不含密钥) */\n readonly label: string\n /**\n * 建一条新的底层传输。返回值是 MCP SDK 的 Transport,对外按 unknown 处理(SDK 类型不出本包);\n * 自定义传输(如测试用的内存传输)也从这里进\n */\n create(): unknown\n}\n\nexport interface McpToolsOptions {\n transport: McpTransport\n /**\n * 给模型看的工具名前缀(如 `\"gh_\"`),多台服务器同名工具靠它区分;缺省无。\n * 调用时按原名发给服务器。宿主工具与 MCP 工具同名时以宿主为准(core 静态贡献的既有规则)\n */\n prefix?: string\n /** 单次 `tools/call` 超时毫秒,缺省 60_000;到点抛错 → 模型看到 isError 结果,循环不崩 */\n callTimeoutMs?: number\n /**\n * run 起步 `tools/list` 失败时怎么办。缺省 false:抛错,run 在写任何日志之前失败(fail-closed,宿主一定知道)。\n * true:本次 run 不贡献工具、告警一次,模型会从工具变化说明里看到这些工具被移除了\n */\n optional?: boolean\n /**\n * 逐工具改写:调整 needsApproval / risk / resultPolicy / description 等;返回 `false` 即不把这个工具给模型。\n * 入参 `tool` 是按缺省规则翻好的 reins Tool,`info` 是服务器原样的声明\n */\n override?(tool: Tool, info: McpToolInfo): Tool | false | undefined\n /** MCP 握手时的客户端自述,缺省 `{ name: \"reins\", version: <本包版本> }` */\n clientInfo?: { name: string; version: string }\n /** 告警出口(optional 降级、工具名被改写),缺省 console.warn */\n warn?: (message: string) => void\n}\n\nexport interface McpToolsSocket extends Socket {\n readonly transport: McpTransport\n /** 关掉底层连接。进程退出、或宿主按请求重建配置时换掉旧的之前调;关掉后再跑 run 会按配方重建 */\n close(): Promise<void>\n}\n\n/** 本包抛出的错误:连接建不起来、`tools/list` 失败、服务器声明不合法 */\nexport class McpToolsError extends Error {\n constructor(\n message: string,\n readonly transport: string,\n options?: { cause?: unknown },\n ) {\n super(message, options)\n this.name = \"McpToolsError\"\n }\n}\n"],"mappings":"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","names":["messageOf"]}
|
package/dist/node.d.ts
CHANGED
|
@@ -1,10 +1,27 @@
|
|
|
1
|
-
import { Tool, Socket } from '@reinsjs/core';
|
|
1
|
+
import { MaybePromise, Tool, Socket } from '@reinsjs/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @reinsjs/tools-mcp 的公开类型。MCP SDK 的类型一律不出本包(同降级层对 pi-ai 的约束):
|
|
5
5
|
* 对外只有 reins 自己的 `Tool` / `Socket`,加上这里几个纯数据形状。
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* 每个请求的 bearer 凭证来源(HTTP 传输)。刻意只有两个方法、且不引用 MCP SDK 的类型:
|
|
10
|
+
* 形状与 SDK 的 `AuthProvider` 结构兼容,宿主不必 import SDK。
|
|
11
|
+
*
|
|
12
|
+
* **不收 SDK 的 `OAuthClientProvider`**:那套的核心是"开浏览器让人点授权",在服务端 agent 里跑不通——
|
|
13
|
+
* 令牌该由宿主的 Web 应用先拿好、存起来,循环里只负责取用与刷新。完整 OAuth 流程仍不封装(0.1 立场不变),
|
|
14
|
+
* 宿主要用可以在 `token` / `onUnauthorized` 里自己调 SDK 的 `refreshAuthorization` 等函数。
|
|
15
|
+
*/
|
|
16
|
+
interface McpAuth {
|
|
17
|
+
/** 每个请求发出前调用。返回 undefined 表示这次不带 Authorization 头 */
|
|
18
|
+
token(): MaybePromise<string | undefined>;
|
|
19
|
+
/**
|
|
20
|
+
* 服务器回 401 时调用,之后传输层**自动重试该请求一次**;重试再 401(或没给这个方法)就抛错。
|
|
21
|
+
* 实现里该做的是刷新令牌——下一次 `token()` 要能返回可用的那个
|
|
22
|
+
*/
|
|
23
|
+
onUnauthorized?(): MaybePromise<void>;
|
|
24
|
+
}
|
|
8
25
|
/** MCP 工具注解(spec 里全部是"提示",只用来给 risk / needsApproval 定缺省值,不当权限判定) */
|
|
9
26
|
interface McpToolAnnotations {
|
|
10
27
|
title?: string;
|
|
@@ -77,4 +94,4 @@ declare class McpToolsError extends Error {
|
|
|
77
94
|
});
|
|
78
95
|
}
|
|
79
96
|
|
|
80
|
-
export { type
|
|
97
|
+
export { type McpAuth as M, type McpTransport as a, type McpToolsOptions as b, type McpToolsSocket as c, type McpToolAnnotations as d, type McpToolInfo as e, McpToolsError as f };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reinsjs/tools-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "reins — MCP servers as a Socket: tools/list once per run, tools/call as Tool.execute, annotations → risk / approval defaults. Streamable HTTP on the Web-standard entry, stdio under ./node",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -35,20 +35,20 @@
|
|
|
35
35
|
"dist",
|
|
36
36
|
"CHANGELOG.md"
|
|
37
37
|
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup"
|
|
40
|
+
},
|
|
38
41
|
"dependencies": {
|
|
39
42
|
"@modelcontextprotocol/client": "2.0.0",
|
|
40
|
-
"@reinsjs/core": "
|
|
43
|
+
"@reinsjs/core": "workspace:*"
|
|
41
44
|
},
|
|
42
45
|
"devDependencies": {
|
|
43
46
|
"@modelcontextprotocol/server": "2.0.0",
|
|
47
|
+
"@reinsjs/brain": "workspace:*",
|
|
44
48
|
"@types/node": "^22.0.0",
|
|
45
|
-
"zod": "^4.2.0"
|
|
46
|
-
"@reinsjs/brain": "0.1.1"
|
|
49
|
+
"zod": "^4.2.0"
|
|
47
50
|
},
|
|
48
51
|
"engines": {
|
|
49
52
|
"node": ">=22"
|
|
50
|
-
},
|
|
51
|
-
"scripts": {
|
|
52
|
-
"build": "tsup"
|
|
53
53
|
}
|
|
54
|
-
}
|
|
54
|
+
}
|