@reinsjs/tools-mcp 0.1.0 → 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 CHANGED
@@ -1,5 +1,35 @@
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
+
25
+ ## 0.1.1
26
+
27
+ ### Patch Changes
28
+
29
+ - Docs only: the umbrella package is `@reinsjs/agent` (npm refused the bare name `reins` as too similar to `redis`). READMEs and the 0.1.0 changelog text now say so; no code changes.
30
+ - Updated dependencies
31
+ - @reinsjs/core@0.1.1
32
+
3
33
  ## 0.1.0
4
34
 
5
35
  ### Minor Changes
@@ -8,11 +38,11 @@
8
38
 
9
39
  - **Timeline first.** Append-only event log with `schemaVersion` on every event and fail-closed upcast on read; projection decides what the model sees each turn; replay and fork from the log alone.
10
40
  - **A loop you can copy.** `runLoop` as an exported async generator: pause/resume is a return value with a small signed state, bounded retry on transient failures only while nothing has been written, tool pipeline `beforeTool → validate → approval → execute → afterTool`, sub-agent pause bubbling (`Interruption { kind: "subagent" }`).
11
- - **Brain modules** (`@reinsjs/brain`): perception, compact (with folded-result manifest and `recall`; recommended on after measuring 100% recall at lower token cost on two model families), pins, spill, handoff, memory (`memory_20250818` shape, namespace isolation), approval (deny → ask → allow, fail-closed), budget (five limits, sub-agent usage included).
41
+ - **Brain modules** (`@reinsjs/brain`): perception, compact (with folded-result manifest and `recall`; recommended on after measuring 100% recall at lower token cost on two model families), pins, spill, handoff, memory (`memory_20250818` shape, namespace isolation), approval (deny → ask → allow, fail-closed), budget (five limits, sub-agent usage included), skills (Agent Skills with progressive disclosure: menu in the system prompt, `skill_read` to open one).
12
42
  - **Lowering** (`@reinsjs/lowering-pi`): Anthropic Messages and OpenAI Responses via pi-ai with a declared loss matrix, trust markers on untrusted content, cache-breakpoint handling.
13
43
  - **Serving** (`@reinsjs/server`, `@reinsjs/ui-agui`): Web-standard handler with SSE replay from `lastSeq`, per-session run lock, `authorizeSession` (only `true` allows), AG-UI as the first-class UI protocol.
14
44
  - **Stores**: SQLite (`node:sqlite` / `bun:sqlite`) and Postgres (`pg` / PGlite), conformance-tested; configurable memory table for per-role isolation.
15
- - **Tools**: MCP servers as one socket (`@reinsjs/tools-mcp`, tools bound per run, annotations as defaults not permissions); `asTool(agent)` in `reins`.
45
+ - **Tools**: MCP servers as one socket (`@reinsjs/tools-mcp`, tools bound per run, annotations as defaults not permissions); `asTool(agent)` in `@reinsjs/agent`.
16
46
  - **TanStack AI** middleware (`@reinsjs/adapter-tanstack-ai`): the brain on TanStack's loop with the log as the single source of truth; idempotent client-message import; runtime check for the approval interrupt.
17
47
  - **Eval harness** (`@reinsjs/eval`): fixtures from real recordings, recorded tools, arm-vs-arm runner, metrics, the four-rule gate.
18
48
 
package/README.md CHANGED
@@ -7,7 +7,7 @@ them exactly like in-process tools. The server never learns that reins exists.
7
7
  ```ts
8
8
  import { httpTransport, mcpTools } from "@reinsjs/tools-mcp"
9
9
  import { approval, spill } from "@reinsjs/brain"
10
- import { createAgent } from "reins"
10
+ import { createAgent } from "@reinsjs/agent"
11
11
 
12
12
  const github = mcpTools({
13
13
  transport: httpTransport({
@@ -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`). Only `/node` imports
40
- `node:*`.
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 was not tested
111
- directly (no key); an aggregator's Claude endpoint returned a truncated stream, a known quirk of that proxy rather
112
- than a protocol rejection. Script: `spikes/mcp-removed-tool-history`.
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 in 0.1: sampling, elicitation, resources, prompts, MCP Apps, OAuth flows (bring your own `fetch` or
130
- `headers`).
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 McpTransport, a as McpToolsOptions, b as McpToolsSocket, c as McpToolAnnotations, d as McpToolInfo } from './types-CUUDQMVs.js';
2
- export { e as McpToolsError } from './types-CUUDQMVs.js';
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 \u5DE5\u5177\u58F0\u660E\u4E0D\u662F\u5BF9\u8C61");
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) throw new TypeError("MCP \u5DE5\u5177\u58F0\u660E\u7F3A\u5C11 name");
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 \u5DE5\u5177 ${t.name} \u7F3A\u5C11 inputSchema`);
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 \u670D\u52A1\u5668\u8FDE\u63A5\u5931\u8D25\uFF08${this.transport.label}\uFF09\uFF1A${messageOf(err)}`,
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 \u5931\u8D25\uFF08${this.transport.label}\uFF09\uFF1A${messageOf(err)}`,
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) throw new RangeError("callTimeoutMs \u5FC5\u987B\u662F\u6B63\u6570");
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(`name:${info.name}`, `MCP \u5DE5\u5177\u540D ${info.name} \u4E0D\u7B26\u5408\u6A21\u578B\u4FA7\u8981\u6C42\uFF0C\u7ED9\u6A21\u578B\u770B\u7684\u540D\u5B57\u6539\u5199\u4E3A ${name}`);
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("list", `MCP \u670D\u52A1\u5668 ${transport.label} \u4E0D\u53EF\u7528\uFF0C\u672C\u6B21 run \u4E0D\u5E26\u5B83\u7684\u5DE5\u5177\uFF1A${messageOf2(err)}`);
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,4 +1,4 @@
1
- import { M as McpTransport } from './types-CUUDQMVs.js';
1
+ import { a as McpTransport } from './types-F_1Kfslp.js';
2
2
  import '@reinsjs/core';
3
3
 
4
4
  interface StdioTransportOptions {
@@ -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 McpTransport as M, type McpToolsOptions as a, type McpToolsSocket as b, type McpToolAnnotations as c, type McpToolInfo as d, McpToolsError as e };
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.1.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": "0.1.0"
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.0"
49
+ "zod": "^4.2.0"
47
50
  },
48
51
  "engines": {
49
52
  "node": ">=22"
50
- },
51
- "scripts": {
52
- "build": "tsup"
53
53
  }
54
- }
54
+ }