@reinsjs/tools-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +134 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +285 -0
- package/dist/index.js.map +1 -0
- package/dist/node.d.ts +15 -0
- package/dist/node.js +20 -0
- package/dist/node.js.map +1 -0
- package/dist/types-CUUDQMVs.d.ts +80 -0
- package/package.json +54 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# @reinsjs/tools-mcp
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- First public release.
|
|
8
|
+
|
|
9
|
+
- **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
|
+
- **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).
|
|
12
|
+
- **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
|
+
- **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
|
+
- **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`.
|
|
16
|
+
- **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
|
+
- **Eval harness** (`@reinsjs/eval`): fixtures from real recordings, recorded tools, arm-vs-arm runner, metrics, the four-rule gate.
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- Updated dependencies
|
|
22
|
+
- @reinsjs/core@0.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NewRate Limited
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# @reinsjs/tools-mcp
|
|
2
|
+
|
|
3
|
+
Plug an [MCP](https://modelcontextprotocol.io) server into a reins agent as one `Socket`. Every tool the server
|
|
4
|
+
lists becomes a reins `Tool`; the loop, the brain modules (spill, approval, budget, compact…) and the timeline treat
|
|
5
|
+
them exactly like in-process tools. The server never learns that reins exists.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { httpTransport, mcpTools } from "@reinsjs/tools-mcp"
|
|
9
|
+
import { approval, spill } from "@reinsjs/brain"
|
|
10
|
+
import { createAgent } from "reins"
|
|
11
|
+
|
|
12
|
+
const github = mcpTools({
|
|
13
|
+
transport: httpTransport({
|
|
14
|
+
url: "https://mcp.example.com/mcp",
|
|
15
|
+
headers: { Authorization: `Bearer ${process.env.MCP_TOKEN}` },
|
|
16
|
+
}),
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
export const agent = createAgent({
|
|
20
|
+
model,
|
|
21
|
+
store,
|
|
22
|
+
tools: [myInProcessTool],
|
|
23
|
+
sockets: [github, spill(), approval()],
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Node-only stdio servers (a subprocess speaking JSON-RPC over stdin/stdout) live behind the `/node` entry:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { stdioTransport } from "@reinsjs/tools-mcp/node"
|
|
31
|
+
|
|
32
|
+
const files = mcpTools({
|
|
33
|
+
transport: stdioTransport({ command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "/data"] }),
|
|
34
|
+
prefix: "fs_",
|
|
35
|
+
})
|
|
36
|
+
```
|
|
37
|
+
|
|
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:*`.
|
|
41
|
+
|
|
42
|
+
## What happens at run time
|
|
43
|
+
|
|
44
|
+
**Tools are bound per run.** When a run starts, the socket calls `tools/list` once and turns the result into the
|
|
45
|
+
run's tool table. The table does not change until the run ends — a `notifications/tools/list_changed` from the
|
|
46
|
+
server only affects the *next* run. This is deliberate: a stable tool table is what keeps prompt caches warm, and it
|
|
47
|
+
is where reins' "no dynamic registration" stance lands. The loop records the table in a `core.tools_bound` event
|
|
48
|
+
(invisible to the model) and, when it differs from the previous run's, appends a visible `system_note` naming the
|
|
49
|
+
added and removed tools so the model knows its hands changed.
|
|
50
|
+
|
|
51
|
+
**Calls go through `tools/call`.** The result's `content` blocks become reins content parts (text and images as-is;
|
|
52
|
+
audio, resource links and binary resources become one-line descriptions — lossy, and said so). `isError` passes
|
|
53
|
+
through. A thrown error (unknown tool, dropped connection, timeout) becomes a `tool_result` with `isError: true`;
|
|
54
|
+
the run continues and the model decides what to do.
|
|
55
|
+
|
|
56
|
+
**Annotations set defaults, not permissions.** `readOnlyHint` → `risk: "low"`; `destructiveHint` → `risk: "high"` and
|
|
57
|
+
`needsApproval: true`; anything else (including no annotations) → `risk: "medium"`. The MCP spec says annotations are
|
|
58
|
+
hints and that untrusted servers' annotations must not drive authorization — so reins only uses them to *default*
|
|
59
|
+
the fields that `@reinsjs/brain`'s `approval()` policies read, and `override` lets the host change any of it:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
mcpTools({
|
|
63
|
+
transport,
|
|
64
|
+
override: (tool, info) => {
|
|
65
|
+
if (info.name === "delete_repo") return false // never expose this one
|
|
66
|
+
if (info.annotations?.openWorldHint) return { ...tool, needsApproval: true }
|
|
67
|
+
return undefined // keep the default translation
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Note the spec's own default for `destructiveHint` is `true` when absent. reins does not treat "unannotated" as
|
|
73
|
+
"destructive" — that would put every tool of every casual server behind an approval prompt. If you want that
|
|
74
|
+
strictness, add it in `override` or run `approval({ unmatched: "ask" })`.
|
|
75
|
+
|
|
76
|
+
## Connection lifecycle
|
|
77
|
+
|
|
78
|
+
`mcpTools()` does not connect. The first run that needs the tool table opens the connection; later runs reuse it.
|
|
79
|
+
If the transport closes (server restarted, network dropped), the next call that needs it — a `tools/call` in the
|
|
80
|
+
same run or `tools/list` at the next run start — rebuilds the connection from the transport recipe once. There is
|
|
81
|
+
no retry policy inside a call: a failed call is reported to the model as an error result, and a failed reconnect at
|
|
82
|
+
run start throws before anything is written to the log (fail-closed). Pass `optional: true` to instead start the
|
|
83
|
+
run without that server's tools and warn once; the model then sees them listed as removed.
|
|
84
|
+
|
|
85
|
+
Call `close()` when you are done with a socket (process shutdown, or the host replaced the configuration).
|
|
86
|
+
|
|
87
|
+
Hosts that build the agent per request should **cache sockets by configuration**, not rebuild them every time:
|
|
88
|
+
rebuilding forces a fresh MCP handshake per request. `examples/mcp/agent.ts` shows a small cache keyed by the
|
|
89
|
+
server's config JSON, closing sockets whose config disappeared.
|
|
90
|
+
|
|
91
|
+
## Hot reload without restarts
|
|
92
|
+
|
|
93
|
+
There is no reload API because none is needed. `createAgent()` only assembles objects, and the tool table is
|
|
94
|
+
resolved at run start. A platform that stores each user's MCP servers in a database reads that configuration
|
|
95
|
+
when a request arrives, builds (or fetches from cache) the matching `mcpTools()` sockets, and runs. A server
|
|
96
|
+
added or removed by the user takes effect on their next message; the loop tells the model what changed.
|
|
97
|
+
|
|
98
|
+
One consequence to know about: a run paused for approval is signed with the tool table it had. If the
|
|
99
|
+
configuration changes while it is paused, resuming is rejected as configuration drift (`RunStateError`
|
|
100
|
+
`config_mismatch`). Hosts have three honest options: wait for the session to finish before applying the change,
|
|
101
|
+
accept that the paused run is abandoned and start a new one, or pass `allowConfigDrift: true` to resume anyway —
|
|
102
|
+
that resume also emits the tool-change note. What the library will not do is silently resume with a different
|
|
103
|
+
tool table than the one the approver saw.
|
|
104
|
+
|
|
105
|
+
## Verified upstream behaviour
|
|
106
|
+
|
|
107
|
+
Requests whose history contains `tool_use` / `tool_result` for a tool that is no longer in the tool table were
|
|
108
|
+
accepted by DeepSeek's Anthropic-protocol endpoint and by the OpenAI Responses protocol (both with another tool
|
|
109
|
+
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`.
|
|
113
|
+
|
|
114
|
+
## Options
|
|
115
|
+
|
|
116
|
+
| Option | Default | Meaning |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| `transport` | — | `httpTransport({ url, headers?, fetch?, requestInit? })` or `stdioTransport({ command, args?, env?, cwd?, stderr? })` |
|
|
119
|
+
| `prefix` | none | Prepended to tool names shown to the model; the server still sees the original name |
|
|
120
|
+
| `callTimeoutMs` | 60 000 | Per `tools/call` timeout; expiry becomes an error result |
|
|
121
|
+
| `optional` | `false` | On `tools/list` failure at run start: throw (default) or contribute no tools and warn once |
|
|
122
|
+
| `override(tool, info)` | — | Per-tool rewrite; return `false` to drop the tool |
|
|
123
|
+
| `clientInfo` | `{ name: "reins", version }` | Sent in the MCP handshake |
|
|
124
|
+
| `warn` | `console.warn` | Where degradations are reported |
|
|
125
|
+
|
|
126
|
+
Tool names that the model APIs reject (`^[A-Za-z0-9_-]{1,64}$`) are rewritten (`files.read` → `files_read`) with a
|
|
127
|
+
one-time warning; the original name is used on the wire.
|
|
128
|
+
|
|
129
|
+
Not in 0.1: sampling, elicitation, resources, prompts, MCP Apps, OAuth flows (bring your own `fetch` or
|
|
130
|
+
`headers`).
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
MIT © 2026 NewRate Limited.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
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';
|
|
3
|
+
import { Tool, ContentPart } from '@reinsjs/core';
|
|
4
|
+
|
|
5
|
+
interface HttpTransportOptions {
|
|
6
|
+
url: string | URL;
|
|
7
|
+
/** 每个请求附带的头(鉴权等) */
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
/** 自定义 fetch(代理、测试里直连内存服务器) */
|
|
10
|
+
fetch?: typeof fetch;
|
|
11
|
+
/** 其余 RequestInit 字段(headers 之外) */
|
|
12
|
+
requestInit?: Omit<RequestInit, "headers">;
|
|
13
|
+
}
|
|
14
|
+
declare function httpTransport(options: HttpTransportOptions): McpTransport;
|
|
15
|
+
|
|
16
|
+
declare const DEFAULT_CALL_TIMEOUT_MS = 60000;
|
|
17
|
+
/** MCP initialize 握手里报给服务器的客户端身份;版本跟随 core,不另维护一份 */
|
|
18
|
+
declare const REINS_MCP_CLIENT_INFO: {
|
|
19
|
+
name: string;
|
|
20
|
+
version: string;
|
|
21
|
+
};
|
|
22
|
+
declare function mcpTools(options: McpToolsOptions): McpToolsSocket;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* MCP ⇄ reins 的纯函数翻译:工具声明 → `Tool` 的静态部分,`tools/call` 结果 → `ContentPart[]`。
|
|
26
|
+
* 不碰网络、不碰事件,单测直接喂 JSON。
|
|
27
|
+
*
|
|
28
|
+
* 有损的地方都在这里明说(P7):reins 的 ContentPart 只有文本与图片,MCP 的 audio / 二进制 resource
|
|
29
|
+
* 翻成一行说明文字告诉模型"有这么个东西但看不到",不静默丢。
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/** Anthropic / OpenAI 对工具名的共同要求;MCP 没有这个限制,不合规的名字要改写,否则请求 400 */
|
|
33
|
+
declare const MODEL_TOOL_NAME_RE: RegExp;
|
|
34
|
+
/** 给模型看的名字:加前缀、把不合规字符换成 `_`、截到 64。返回值与原名不同则调用方应告警一次 */
|
|
35
|
+
declare function modelToolName(mcpName: string, prefix?: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* 注解 → 风险档。spec 说注解只是提示、不可信服务器的注解不能当权限依据,所以这里只定**缺省值**:
|
|
38
|
+
* 明说只读 → low;明说破坏性 → high(并缺省要审批);其余(含完全没注解)→ medium,交给 approval 模块的策略。
|
|
39
|
+
* 注意 spec 里 destructiveHint 缺省为 true,我们刻意不把"没写"当"破坏性"——否则每个 MCP 工具都要审批,
|
|
40
|
+
* 宿主要那么严可以用 `override` 自己定。
|
|
41
|
+
*/
|
|
42
|
+
declare function riskOf(annotations: McpToolAnnotations | undefined): NonNullable<Tool["risk"]>;
|
|
43
|
+
/** `tools/list` 里的一项 → 纯数据 McpToolInfo;形状不对就抛(服务器声明不合法,宁可起步失败) */
|
|
44
|
+
declare function toToolInfo(raw: unknown): McpToolInfo;
|
|
45
|
+
/** `tools/call` 结果的形状(只取我们用到的字段;SDK 类型不出本包) */
|
|
46
|
+
interface McpCallResult {
|
|
47
|
+
content?: unknown;
|
|
48
|
+
structuredContent?: unknown;
|
|
49
|
+
isError?: unknown;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* MCP 内容块 → reins 内容片段。
|
|
53
|
+
* - text → 文本;image → 图片(mime + base64)
|
|
54
|
+
* - audio → 一行说明(模型看不到音频)
|
|
55
|
+
* - resource_link → 一行 "Resource link: uri (name) [mime]"
|
|
56
|
+
* - resource(内嵌):文本资源 → 带 uri 头的文本;图片二进制 → 图片;其他二进制 → 一行说明
|
|
57
|
+
* - 不认识的块 → JSON 文本(不静默丢)
|
|
58
|
+
* - content 为空而有 structuredContent → 其 JSON;都没有 → 空文本(模型总得有个结果块)
|
|
59
|
+
*/
|
|
60
|
+
declare function toContentParts(result: McpCallResult): ContentPart[];
|
|
61
|
+
|
|
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 };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// src/http.ts
|
|
2
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
|
|
3
|
+
function httpTransport(options) {
|
|
4
|
+
const url = options.url instanceof URL ? options.url : new URL(options.url);
|
|
5
|
+
const label = `${url.origin}${url.pathname}`;
|
|
6
|
+
return {
|
|
7
|
+
kind: "http",
|
|
8
|
+
label,
|
|
9
|
+
create: () => new StreamableHTTPClientTransport(url, {
|
|
10
|
+
requestInit: { ...options.requestInit, ...options.headers ? { headers: options.headers } : {} },
|
|
11
|
+
...options.fetch ? { fetch: options.fetch } : {}
|
|
12
|
+
})
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/mcp-tools.ts
|
|
17
|
+
import { defineTool, REINS_VERSION } from "@reinsjs/core";
|
|
18
|
+
|
|
19
|
+
// src/connection.ts
|
|
20
|
+
import { Client } from "@modelcontextprotocol/client";
|
|
21
|
+
|
|
22
|
+
// src/translate.ts
|
|
23
|
+
var MODEL_TOOL_NAME_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
24
|
+
function modelToolName(mcpName, prefix = "") {
|
|
25
|
+
const raw = `${prefix}${mcpName}`;
|
|
26
|
+
if (MODEL_TOOL_NAME_RE.test(raw)) return raw;
|
|
27
|
+
const cleaned = raw.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 64);
|
|
28
|
+
return cleaned.length > 0 ? cleaned : "_";
|
|
29
|
+
}
|
|
30
|
+
function riskOf(annotations) {
|
|
31
|
+
if (annotations?.destructiveHint === true) return "high";
|
|
32
|
+
if (annotations?.readOnlyHint === true) return "low";
|
|
33
|
+
return "medium";
|
|
34
|
+
}
|
|
35
|
+
function toToolInfo(raw) {
|
|
36
|
+
if (typeof raw !== "object" || raw === null) throw new TypeError("MCP \u5DE5\u5177\u58F0\u660E\u4E0D\u662F\u5BF9\u8C61");
|
|
37
|
+
const t = raw;
|
|
38
|
+
if (typeof t.name !== "string" || t.name.length === 0) throw new TypeError("MCP \u5DE5\u5177\u58F0\u660E\u7F3A\u5C11 name");
|
|
39
|
+
if (typeof t.inputSchema !== "object" || t.inputSchema === null)
|
|
40
|
+
throw new TypeError(`MCP \u5DE5\u5177 ${t.name} \u7F3A\u5C11 inputSchema`);
|
|
41
|
+
const info = { name: t.name, inputSchema: t.inputSchema };
|
|
42
|
+
if (typeof t.title === "string") info.title = t.title;
|
|
43
|
+
if (typeof t.description === "string") info.description = t.description;
|
|
44
|
+
if (typeof t.outputSchema === "object" && t.outputSchema !== null)
|
|
45
|
+
info.outputSchema = t.outputSchema;
|
|
46
|
+
if (typeof t.annotations === "object" && t.annotations !== null)
|
|
47
|
+
info.annotations = t.annotations;
|
|
48
|
+
return info;
|
|
49
|
+
}
|
|
50
|
+
function base64Bytes(b64) {
|
|
51
|
+
const padding = b64.endsWith("==") ? 2 : b64.endsWith("=") ? 1 : 0;
|
|
52
|
+
return Math.max(0, Math.floor(b64.length * 3 / 4) - padding);
|
|
53
|
+
}
|
|
54
|
+
function toContentParts(result) {
|
|
55
|
+
const blocks = Array.isArray(result.content) ? result.content : [];
|
|
56
|
+
const parts = [];
|
|
57
|
+
for (const raw of blocks) {
|
|
58
|
+
const b = typeof raw === "object" && raw !== null ? raw : {};
|
|
59
|
+
switch (b.type) {
|
|
60
|
+
case "text":
|
|
61
|
+
parts.push({ type: "text", text: typeof b.text === "string" ? b.text : "" });
|
|
62
|
+
break;
|
|
63
|
+
case "image":
|
|
64
|
+
if (typeof b.data === "string" && typeof b.mimeType === "string")
|
|
65
|
+
parts.push({ type: "image", mime: b.mimeType, data: b.data });
|
|
66
|
+
else parts.push({ type: "text", text: "[image block without data/mimeType]" });
|
|
67
|
+
break;
|
|
68
|
+
case "audio": {
|
|
69
|
+
const bytes = typeof b.data === "string" ? base64Bytes(b.data) : 0;
|
|
70
|
+
parts.push({
|
|
71
|
+
type: "text",
|
|
72
|
+
text: `[audio ${String(b.mimeType ?? "unknown type")}, ${bytes} bytes; audio cannot be shown to the model]`
|
|
73
|
+
});
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
case "resource_link": {
|
|
77
|
+
const name = typeof b.name === "string" ? ` (${b.name})` : "";
|
|
78
|
+
const mime = typeof b.mimeType === "string" ? ` [${b.mimeType}]` : "";
|
|
79
|
+
const desc = typeof b.description === "string" ? ` \u2014 ${b.description}` : "";
|
|
80
|
+
parts.push({ type: "text", text: `Resource link: ${String(b.uri)}${name}${mime}${desc}` });
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case "resource": {
|
|
84
|
+
const r = typeof b.resource === "object" && b.resource !== null ? b.resource : {};
|
|
85
|
+
const uri = String(r.uri ?? "");
|
|
86
|
+
const mime = typeof r.mimeType === "string" ? r.mimeType : void 0;
|
|
87
|
+
if (typeof r.text === "string") {
|
|
88
|
+
parts.push({ type: "text", text: `[resource ${uri}${mime ? ` (${mime})` : ""}]
|
|
89
|
+
${r.text}` });
|
|
90
|
+
} else if (typeof r.blob === "string" && mime?.startsWith("image/")) {
|
|
91
|
+
parts.push({ type: "image", mime, data: r.blob });
|
|
92
|
+
} else {
|
|
93
|
+
const bytes = typeof r.blob === "string" ? base64Bytes(r.blob) : 0;
|
|
94
|
+
parts.push({
|
|
95
|
+
type: "text",
|
|
96
|
+
text: `[binary resource ${uri}${mime ? ` (${mime})` : ""}, ${bytes} bytes; not shown to the model]`
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
default:
|
|
102
|
+
parts.push({ type: "text", text: JSON.stringify(raw) ?? String(raw) });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (parts.length === 0) {
|
|
106
|
+
if (result.structuredContent !== void 0 && result.structuredContent !== null)
|
|
107
|
+
parts.push({ type: "text", text: JSON.stringify(result.structuredContent) ?? "" });
|
|
108
|
+
else parts.push({ type: "text", text: "" });
|
|
109
|
+
}
|
|
110
|
+
return parts;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/types.ts
|
|
114
|
+
var McpToolsError = class extends Error {
|
|
115
|
+
constructor(message, transport, options) {
|
|
116
|
+
super(message, options);
|
|
117
|
+
this.transport = transport;
|
|
118
|
+
this.name = "McpToolsError";
|
|
119
|
+
}
|
|
120
|
+
transport;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// src/connection.ts
|
|
124
|
+
var McpConnection = class {
|
|
125
|
+
constructor(transport, options) {
|
|
126
|
+
this.transport = transport;
|
|
127
|
+
this.options = options;
|
|
128
|
+
}
|
|
129
|
+
transport;
|
|
130
|
+
options;
|
|
131
|
+
client;
|
|
132
|
+
connecting;
|
|
133
|
+
/** 当前是否持有一条活连接(测试与排错用) */
|
|
134
|
+
get connected() {
|
|
135
|
+
return this.client !== void 0;
|
|
136
|
+
}
|
|
137
|
+
/** 拿到已连接的 Client:没有就建;正在建就等同一次;上次断了(onclose 已清掉)就重建 */
|
|
138
|
+
async ensure() {
|
|
139
|
+
if (this.client) return this.client;
|
|
140
|
+
if (this.connecting) return this.connecting;
|
|
141
|
+
this.connecting = (async () => {
|
|
142
|
+
const client = new Client(this.options.clientInfo);
|
|
143
|
+
try {
|
|
144
|
+
await client.connect(this.transport.create());
|
|
145
|
+
} catch (err) {
|
|
146
|
+
throw new McpToolsError(
|
|
147
|
+
`MCP \u670D\u52A1\u5668\u8FDE\u63A5\u5931\u8D25\uFF08${this.transport.label}\uFF09\uFF1A${messageOf(err)}`,
|
|
148
|
+
this.transport.label,
|
|
149
|
+
{ cause: err }
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
client.onclose = () => {
|
|
153
|
+
if (this.client === client) this.client = void 0;
|
|
154
|
+
};
|
|
155
|
+
this.client = client;
|
|
156
|
+
return client;
|
|
157
|
+
})();
|
|
158
|
+
try {
|
|
159
|
+
return await this.connecting;
|
|
160
|
+
} finally {
|
|
161
|
+
this.connecting = void 0;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** `tools/list`(SDK 会自动翻完全部分页)。绕过 SDK 的列表缓存:每次 run 起步要的就是服务器此刻的表 */
|
|
165
|
+
async listTools(signal) {
|
|
166
|
+
const client = await this.ensure();
|
|
167
|
+
let result;
|
|
168
|
+
try {
|
|
169
|
+
result = await client.listTools({}, { cacheMode: "bypass", ...signal ? { signal } : {} });
|
|
170
|
+
} catch (err) {
|
|
171
|
+
throw new McpToolsError(
|
|
172
|
+
`MCP tools/list \u5931\u8D25\uFF08${this.transport.label}\uFF09\uFF1A${messageOf(err)}`,
|
|
173
|
+
this.transport.label,
|
|
174
|
+
{ cause: err }
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return result.tools.map(toToolInfo);
|
|
178
|
+
}
|
|
179
|
+
/** `tools/call`。抛错(未知工具、断连、超时)交给调用方翻成 isError 结果 */
|
|
180
|
+
async callTool(name, args, opts) {
|
|
181
|
+
const client = await this.ensure();
|
|
182
|
+
const result = await client.callTool(
|
|
183
|
+
{ name, arguments: args ?? {} },
|
|
184
|
+
{ timeout: opts.timeoutMs, ...opts.signal ? { signal: opts.signal } : {} }
|
|
185
|
+
);
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* 关掉当前连接(宿主热轮换 Socket、进程收尾)。
|
|
190
|
+
* 正在建连时调用:等这次建连有结果再关——否则 `ensure()` 完成后会把新 Client 挂回 `this.client`,
|
|
191
|
+
* 连接泄漏、`onclose` 回调悬挂(2026-09-10 审查修)。建连失败本来就没有连接,吞掉那个错误。
|
|
192
|
+
*/
|
|
193
|
+
async close() {
|
|
194
|
+
if (this.connecting) {
|
|
195
|
+
try {
|
|
196
|
+
await this.connecting;
|
|
197
|
+
} catch {
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const client = this.client;
|
|
201
|
+
this.client = void 0;
|
|
202
|
+
if (client) await client.close();
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
function messageOf(err) {
|
|
206
|
+
return err instanceof Error ? err.message : String(err);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/mcp-tools.ts
|
|
210
|
+
var DEFAULT_CALL_TIMEOUT_MS = 6e4;
|
|
211
|
+
var REINS_MCP_CLIENT_INFO = { name: "reins", version: REINS_VERSION };
|
|
212
|
+
function mcpTools(options) {
|
|
213
|
+
const { transport } = options;
|
|
214
|
+
const warn = options.warn ?? ((m) => console.warn(m));
|
|
215
|
+
const prefix = options.prefix ?? "";
|
|
216
|
+
const timeoutMs = options.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS;
|
|
217
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new RangeError("callTimeoutMs \u5FC5\u987B\u662F\u6B63\u6570");
|
|
218
|
+
const conn = new McpConnection(transport, { clientInfo: options.clientInfo ?? REINS_MCP_CLIENT_INFO });
|
|
219
|
+
const warned = /* @__PURE__ */ new Set();
|
|
220
|
+
const warnOnce = (key, message) => {
|
|
221
|
+
if (warned.has(key)) return;
|
|
222
|
+
warned.add(key);
|
|
223
|
+
warn(message);
|
|
224
|
+
};
|
|
225
|
+
const toTool = (info) => {
|
|
226
|
+
const name = modelToolName(info.name, prefix);
|
|
227
|
+
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}`);
|
|
229
|
+
const risk = riskOf(info.annotations);
|
|
230
|
+
return defineTool({
|
|
231
|
+
name,
|
|
232
|
+
description: info.description ?? info.title ?? info.name,
|
|
233
|
+
inputSchema: info.inputSchema,
|
|
234
|
+
risk,
|
|
235
|
+
// 明说破坏性的工具缺省要人点头;循环内置兜底会在没有 Socket 做主时直接转审批
|
|
236
|
+
...risk === "high" ? { needsApproval: true } : {},
|
|
237
|
+
async execute(args, ctx) {
|
|
238
|
+
const result = await conn.callTool(info.name, args, {
|
|
239
|
+
timeoutMs,
|
|
240
|
+
...ctx.signal ? { signal: ctx.signal } : {}
|
|
241
|
+
});
|
|
242
|
+
return { content: toContentParts(result), isError: result.isError === true };
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
};
|
|
246
|
+
return {
|
|
247
|
+
name: `mcp:${transport.label}`,
|
|
248
|
+
transport,
|
|
249
|
+
tools: async () => {
|
|
250
|
+
let infos;
|
|
251
|
+
try {
|
|
252
|
+
infos = await conn.listTools();
|
|
253
|
+
} catch (err) {
|
|
254
|
+
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)}`);
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
const tools = [];
|
|
259
|
+
for (const info of infos) {
|
|
260
|
+
const tool = toTool(info);
|
|
261
|
+
const overridden = options.override?.(tool, info);
|
|
262
|
+
if (overridden === false) continue;
|
|
263
|
+
tools.push(overridden ?? tool);
|
|
264
|
+
}
|
|
265
|
+
return tools;
|
|
266
|
+
},
|
|
267
|
+
close: () => conn.close()
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function messageOf2(err) {
|
|
271
|
+
return err instanceof Error ? err.message : String(err);
|
|
272
|
+
}
|
|
273
|
+
export {
|
|
274
|
+
DEFAULT_CALL_TIMEOUT_MS,
|
|
275
|
+
MODEL_TOOL_NAME_RE,
|
|
276
|
+
McpToolsError,
|
|
277
|
+
REINS_MCP_CLIENT_INFO,
|
|
278
|
+
httpTransport,
|
|
279
|
+
mcpTools,
|
|
280
|
+
modelToolName,
|
|
281
|
+
riskOf,
|
|
282
|
+
toContentParts,
|
|
283
|
+
toToolInfo
|
|
284
|
+
};
|
|
285
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"]}
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { M as McpTransport } from './types-CUUDQMVs.js';
|
|
2
|
+
import '@reinsjs/core';
|
|
3
|
+
|
|
4
|
+
interface StdioTransportOptions {
|
|
5
|
+
command: string;
|
|
6
|
+
args?: string[];
|
|
7
|
+
/** 子进程环境;缺省只继承 SDK 认为安全的少数变量(PATH、HOME 等),密钥要显式传 */
|
|
8
|
+
env?: Record<string, string>;
|
|
9
|
+
cwd?: string;
|
|
10
|
+
/** 子进程 stderr 去哪:缺省 "inherit"(打到宿主 stderr),"pipe" / "ignore" 同 child_process */
|
|
11
|
+
stderr?: "inherit" | "pipe" | "ignore";
|
|
12
|
+
}
|
|
13
|
+
declare function stdioTransport(options: StdioTransportOptions): McpTransport;
|
|
14
|
+
|
|
15
|
+
export { McpTransport, type StdioTransportOptions, stdioTransport };
|
package/dist/node.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/node.ts
|
|
2
|
+
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
|
3
|
+
function stdioTransport(options) {
|
|
4
|
+
const label = [options.command, ...options.args ?? []].join(" ");
|
|
5
|
+
return {
|
|
6
|
+
kind: "stdio",
|
|
7
|
+
label,
|
|
8
|
+
create: () => new StdioClientTransport({
|
|
9
|
+
command: options.command,
|
|
10
|
+
...options.args ? { args: options.args } : {},
|
|
11
|
+
...options.env ? { env: options.env } : {},
|
|
12
|
+
...options.cwd ? { cwd: options.cwd } : {},
|
|
13
|
+
...options.stderr ? { stderr: options.stderr } : {}
|
|
14
|
+
})
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
stdioTransport
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=node.js.map
|
package/dist/node.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/node.ts"],"sourcesContent":["/**\n * @reinsjs/tools-mcp/node —— stdio 传输配方:起一个子进程当 MCP 服务器。\n * 只有这个子路径带 Node 依赖(SDK 的 stdio 入口用 node:process / node:stream / cross-spawn);主入口保持纯 Web 标准。\n */\nimport { StdioClientTransport } from \"@modelcontextprotocol/client/stdio\"\nimport type { McpTransport } from \"./types.js\"\n\nexport interface StdioTransportOptions {\n command: string\n args?: string[]\n /** 子进程环境;缺省只继承 SDK 认为安全的少数变量(PATH、HOME 等),密钥要显式传 */\n env?: Record<string, string>\n cwd?: string\n /** 子进程 stderr 去哪:缺省 \"inherit\"(打到宿主 stderr),\"pipe\" / \"ignore\" 同 child_process */\n stderr?: \"inherit\" | \"pipe\" | \"ignore\"\n}\n\nexport function stdioTransport(options: StdioTransportOptions): McpTransport {\n const label = [options.command, ...(options.args ?? [])].join(\" \")\n return {\n kind: \"stdio\",\n label,\n create: () =>\n new StdioClientTransport({\n command: options.command,\n ...(options.args ? { args: options.args } : {}),\n ...(options.env ? { env: options.env } : {}),\n ...(options.cwd ? { cwd: options.cwd } : {}),\n ...(options.stderr ? { stderr: options.stderr } : {}),\n }),\n }\n}\n\nexport type { McpTransport } from \"./types.js\"\n"],"mappings":";AAIA,SAAS,4BAA4B;AAa9B,SAAS,eAAe,SAA8C;AAC3E,QAAM,QAAQ,CAAC,QAAQ,SAAS,GAAI,QAAQ,QAAQ,CAAC,CAAE,EAAE,KAAK,GAAG;AACjE,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,QAAQ,MACN,IAAI,qBAAqB;AAAA,MACvB,SAAS,QAAQ;AAAA,MACjB,GAAI,QAAQ,OAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC;AAAA,MAC7C,GAAI,QAAQ,MAAM,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;AAAA,MAC1C,GAAI,QAAQ,MAAM,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;AAAA,MAC1C,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAAA,IACrD,CAAC;AAAA,EACL;AACF;","names":[]}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Tool, Socket } from '@reinsjs/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @reinsjs/tools-mcp 的公开类型。MCP SDK 的类型一律不出本包(同降级层对 pi-ai 的约束):
|
|
5
|
+
* 对外只有 reins 自己的 `Tool` / `Socket`,加上这里几个纯数据形状。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** MCP 工具注解(spec 里全部是"提示",只用来给 risk / needsApproval 定缺省值,不当权限判定) */
|
|
9
|
+
interface McpToolAnnotations {
|
|
10
|
+
title?: string;
|
|
11
|
+
readOnlyHint?: boolean;
|
|
12
|
+
destructiveHint?: boolean;
|
|
13
|
+
idempotentHint?: boolean;
|
|
14
|
+
openWorldHint?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** `tools/list` 返回的一个工具,原样搬过来的纯数据;`override` 钩子拿它做逐工具改写的依据 */
|
|
17
|
+
interface McpToolInfo {
|
|
18
|
+
name: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
inputSchema: Record<string, unknown>;
|
|
22
|
+
outputSchema?: Record<string, unknown>;
|
|
23
|
+
annotations?: McpToolAnnotations;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 一条 MCP 传输的"配方":怎么建出底层连接。主入口只有 `httpTransport`(Streamable HTTP,纯 Web 标准),
|
|
27
|
+
* `@reinsjs/tools-mcp/node` 才有 `stdioTransport`(起子进程)。连接本身由 `mcpTools()` 懒建、跨 run 复用,
|
|
28
|
+
* 断了在下一次需要时按这个配方重建一次 —— 所以配方必须可重复调用。
|
|
29
|
+
*/
|
|
30
|
+
interface McpTransport {
|
|
31
|
+
readonly kind: "http" | "stdio" | "custom";
|
|
32
|
+
/** 给 Socket.name、告警与报错用的可读标签(不含密钥) */
|
|
33
|
+
readonly label: string;
|
|
34
|
+
/**
|
|
35
|
+
* 建一条新的底层传输。返回值是 MCP SDK 的 Transport,对外按 unknown 处理(SDK 类型不出本包);
|
|
36
|
+
* 自定义传输(如测试用的内存传输)也从这里进
|
|
37
|
+
*/
|
|
38
|
+
create(): unknown;
|
|
39
|
+
}
|
|
40
|
+
interface McpToolsOptions {
|
|
41
|
+
transport: McpTransport;
|
|
42
|
+
/**
|
|
43
|
+
* 给模型看的工具名前缀(如 `"gh_"`),多台服务器同名工具靠它区分;缺省无。
|
|
44
|
+
* 调用时按原名发给服务器。宿主工具与 MCP 工具同名时以宿主为准(core 静态贡献的既有规则)
|
|
45
|
+
*/
|
|
46
|
+
prefix?: string;
|
|
47
|
+
/** 单次 `tools/call` 超时毫秒,缺省 60_000;到点抛错 → 模型看到 isError 结果,循环不崩 */
|
|
48
|
+
callTimeoutMs?: number;
|
|
49
|
+
/**
|
|
50
|
+
* run 起步 `tools/list` 失败时怎么办。缺省 false:抛错,run 在写任何日志之前失败(fail-closed,宿主一定知道)。
|
|
51
|
+
* true:本次 run 不贡献工具、告警一次,模型会从工具变化说明里看到这些工具被移除了
|
|
52
|
+
*/
|
|
53
|
+
optional?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* 逐工具改写:调整 needsApproval / risk / resultPolicy / description 等;返回 `false` 即不把这个工具给模型。
|
|
56
|
+
* 入参 `tool` 是按缺省规则翻好的 reins Tool,`info` 是服务器原样的声明
|
|
57
|
+
*/
|
|
58
|
+
override?(tool: Tool, info: McpToolInfo): Tool | false | undefined;
|
|
59
|
+
/** MCP 握手时的客户端自述,缺省 `{ name: "reins", version: <本包版本> }` */
|
|
60
|
+
clientInfo?: {
|
|
61
|
+
name: string;
|
|
62
|
+
version: string;
|
|
63
|
+
};
|
|
64
|
+
/** 告警出口(optional 降级、工具名被改写),缺省 console.warn */
|
|
65
|
+
warn?: (message: string) => void;
|
|
66
|
+
}
|
|
67
|
+
interface McpToolsSocket extends Socket {
|
|
68
|
+
readonly transport: McpTransport;
|
|
69
|
+
/** 关掉底层连接。进程退出、或宿主按请求重建配置时换掉旧的之前调;关掉后再跑 run 会按配方重建 */
|
|
70
|
+
close(): Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
/** 本包抛出的错误:连接建不起来、`tools/list` 失败、服务器声明不合法 */
|
|
73
|
+
declare class McpToolsError extends Error {
|
|
74
|
+
readonly transport: string;
|
|
75
|
+
constructor(message: string, transport: string, options?: {
|
|
76
|
+
cause?: unknown;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
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 };
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@reinsjs/tools-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"keywords": [
|
|
6
|
+
"agent",
|
|
7
|
+
"mcp",
|
|
8
|
+
"model-context-protocol",
|
|
9
|
+
"tools",
|
|
10
|
+
"streamable-http",
|
|
11
|
+
"stdio"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "NewRate Limited (https://www.adrate.io)",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/AdRate-io/reins.git",
|
|
18
|
+
"directory": "packages/tools-mcp"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/AdRate-io/reins/tree/main/packages/tools-mcp#readme",
|
|
21
|
+
"bugs": "https://github.com/AdRate-io/reins/issues",
|
|
22
|
+
"type": "module",
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./node": {
|
|
30
|
+
"types": "./dist/node.d.ts",
|
|
31
|
+
"import": "./dist/node.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"CHANGELOG.md"
|
|
37
|
+
],
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@modelcontextprotocol/client": "2.0.0",
|
|
40
|
+
"@reinsjs/core": "0.1.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@modelcontextprotocol/server": "2.0.0",
|
|
44
|
+
"@types/node": "^22.0.0",
|
|
45
|
+
"zod": "^4.2.0",
|
|
46
|
+
"@reinsjs/brain": "0.1.0"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=22"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsup"
|
|
53
|
+
}
|
|
54
|
+
}
|