@reinsjs/tools-mcp

中文文档
0.3.3发布于 2026/11/02MITTypeScriptES Module

缰绳- MCP服务器作为套接字:每次运行一次工具/列表,工具/调用作为工具。执行、注释→风险/审批默认值。在web标准条目上的可流式HTTP,在。/node下的studio

中文使用文档
文档目录9 节
  1. 在运行时发生了什么
  2. 连接的生命周期
  3. 不重启的热重新加载
  4. 证书到期
  5. Gateway-style服务器
  6. 大型平面工作台
  7. 已验证的上游行为
  8. 选项
  9. 许可证

@reinsjs / tools-mcp

插头一个 MCP 将服务器作为一个REINS代理 Socket. 每个工具服务器 清单变成了缰绳 Tool;循环,大脑模块(泄漏,批准,预算,压缩……)和时间线治疗 它们就像进程内工具一样。服务器永远不会知道缰绳的存在。

import { httpTransport, mcpTools } from "@reinsjs/tools-mcp"
import { approval, spill } from "@reinsjs/brain"
import { createAgent } from "@reinsjs/agent"

const github = mcpTools({
  transport: httpTransport({
    url: "https://mcp.example.com/mcp",
    headers: { Authorization: `Bearer ${process.env.MCP_TOKEN}` },
  }),
})

export const agent = createAgent({
  model,
  store,
  tools: [myInProcessTool],
  sockets: [github, spill(), approval()],
})

仅节点的工作室服务器(通过stdin/stdout说JSON-RPC的子进程)位于 /node 条目:

import { stdioTransport } from "@reinsjs/tools-mcp/node"

const files = mcpTools({
  transport: stdioTransport({ command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "/data"] }),
  prefix: "fs_",
})

主入口只使用web标准api (fetch, URL, Headers),并在Cloudflare Workers上运行 nodejs_compat (在2023兼容性日期验证,请参阅 spikes/edge-runtime-check),在Bun上是Deno (--allow-net 就够了),而Vercel的呢 edge-runtime (spikes/runtime-matrix - - - - - - tools/list + tools/call 针对每个服务器中的本地服务器)。只有 /node 导入 node:*.

在运行时发生了什么

每次运行都绑定工具。 当运行开始时,套接字调用 tools/list 一次,把结果变成 运行工具表。表不会改变,直到运行结束- a notifications/tools/list_changed 从 服务器只影响 下一个 运行。这是经过深思熟虑的:稳定的工具表可以使提示缓存保持温暖,并且它可以使缓存保持温暖 这就是缰绳“不动态注册”立场的来源。循环将表记录在a中 core.tools_bound 事件 (对模型不可见),并且当它与之前运行的不同时,附加一个可见的 system_note 命名的 添加和删除工具,使模型知道它的手的变化。

电话接通 tools/call. 结果的 content 块成为缰绳内容部分(文本和图像按原样; 音频、资源链接和二进制资源变成了一行描述(有损,并如此说)。 isError 通过 通过。抛出的错误(未知工具、断开连接、超时)变成 tool_result 与 isError: true; 运行继续,模型决定做什么。

注释设置默认值,而不是权限。 readOnlyHint → risk: "low"; destructiveHint → risk: "high" 和 needsApproval: true;其他任何内容(包括没有注释)→ risk: "medium". MCP规范说注释是 提示和不受信任的服务器的注释不能驱动授权-因此缰绳只使用它们来 默认的 这些领域 @reinsjs/brain的 approval() 政策阅读,以及 override 允许主机更改其中任何一个:

mcpTools({
  transport,
  override: (tool, info) => {
    if (info.name === "delete_repo") return false // never expose this one
    if (info.annotations?.openWorldHint) return { ...tool, needsApproval: true }
    return undefined // keep the default translation
  },
})

注意规范自己的默认for destructiveHint 是 true 当缺席。缰绳不将“未注释”视为 “破坏性的”——这将使每个休闲服务器的每个工具都处于审批提示之后。如果你想要的话 严格,加进去 override 或运行 approval({ unmatched: "ask" }).

连接的生命周期

mcpTools() 不连接。需要工具表的第一次运行打开连接;以后运行重用它。 如果传输关闭(服务器重新启动,网络断开),则需要它的下一个调用- a tools/call 在 相同的运行或 tools/list 在下次运行开始时-根据传输配方重新构建连接一次。有 调用内部没有重试策略:失败的调用作为错误结果报告给模型,失败的重新连接在 Run start在向日志写入任何内容之前抛出(失败关闭)。通过 optional: true 来代替 运行没有服务器的工具,并警告一次;然后,模型看到它们被列为已删除。

呼叫 close() 当您使用套接字时(进程关闭,或者主机替换了配置)。

为每个请求构建代理的主机应该 按配置设置缓存套接字,而不是每次都重建它们。 重建对每个请求强制进行一次新的MCP握手。 examples/mcp/agent.ts 控件键控的小缓存 服务器的配置JSON,关闭配置丢失的套接字。

不重启的热重新加载

没有重载API,因为不需要重载API。 createAgent() 只组装对象,工具表是 在运行开始时解决。将每个用户的MCP服务器存储在数据库中的平台读取该配置 当请求到达时,构建(或从缓存中获取)匹配 mcpTools() 套接字和运行。一个服务器 用户添加或删除的内容在其下一条消息中生效;这个循环告诉模型发生了什么变化。

需要了解的一个结果是:暂停审批的运行使用它所拥有的工具表进行签名。如果 暂停时配置发生更改,恢复被拒绝为配置漂移(RunStateError config_mismatch)。主机有三个诚实的选择:等待会话结束后再应用更改; 接受暂停的运行被放弃并开始一个新的运行,或者通过 allowConfigDrift: true 无论如何要继续—— 这份简历还会附上工具变更说明。什么图书馆不会做的是默默地恢复与不同 工具表比审批人看到的要多。

证书到期

一个固定的令牌可以驻留 headers. 过期的令牌不能: headers 在运输工具建成时是固定的, 虽然连接是惰性创建的,但可以跨运行重用,并在删除后从相同的recipe重新构建—所以只有一次 令牌过期,即使重新构建也会携带过期的令牌。通过 auth 而不是:

httpTransport({
  url: "https://business-api.tiktok.com/open_mcp/tt-ads-mcp-layer",
  auth: {
    token: () => db.accessToken(userId),          // called before every request
    onUnauthorized: () => db.refresh(userId),     // called on 401, then the request is retried once
  },
})

auth 和一个 Authorization 头是互斥的——在构造时传递两个抛出,而不是 让一个人默默获胜。

McpAuth 就是这两个方法,没有别的,也没有引用任何MCP SDK类型。事实并非如此 SDK的 OAuthClientProvider该界面的重要部分是驱动浏览器授权提示, 这是服务器端代理无法做到的。获取并存储令牌在您的web应用程序;循环只读取和 刷新。完整的OAuth流仍然没有包装-调用SDK refreshAuthorization (或者你自己的代码) 从内 token / onUnauthorized.

Gateway-style服务器

一些服务器将数百个操作抛在脑后 一个 调度工具。TikTok for Business是当前的趋势 示例:它的分层端点注册了41个普通工具 tool_list / tool_get / tool_execute,和 其他约330个操作仅存在于服务器端注册表中,通过 tool_execute({ tool_name, params }).

这不会对循环造成任何损失——工具表在整个运行过程中是固定的,因此提示缓存前缀和配置散列是固定的 不受影响。 但是它无声地挫败了针对工具名称编写的批准策略。 创建一个活动, 修改预算和删除资产组都被称为 tool_execute;实际发生的是 参数。

// WRONG — this never matches. Every operation is named tool_execute.
approval({ deny: ["*_delete"], allow: ["*"] })

// RIGHT — decide on the operation name inside the arguments.
const operationOf = (args: unknown): string =>
  typeof args === "object" && args !== null && "tool_name" in args
    ? String((args as { tool_name?: unknown }).tool_name ?? "")
    : ""

approval({
  ask: [{
    id: "gateway.writes",
    match: (call) =>
      call.name === "tool_execute" && /_(create|update|delete|upload)$/.test(operationOf(call.args)),
    summary: (call) => `TikTok ${operationOf(call.args)}`,   // the approver sees the real operation
  }],
  allow: [
    { id: "gateway.reads", match: (call) =>
        call.name === "tool_execute" && /_(get|list|search)$/.test(operationOf(call.args)) },
    "tool_list",
    "tool_get",
  ],
})

此配方的可执行版本为 packages/brain/src/gateway-tool.recipe.test.ts,包括负面的 证明基于名称的规则允许删除的案例。

在没有配置规则的情况下,默认值是安全的,但很生硬:这样的服务器不声明注释,因此可以翻译 工具不携带 risk, unmatched: "byRisk" 发送 每一个 呼叫-包括读取-到一个人。这是 故障关闭设计;使用上面的recipe打开读路径,而不是允许dispatcher批发。

关于这种形状的服务器,还有两件事值得了解:

  • 注册表列表可能很大。 TikTok的 tool_list 返回~57 KB。下 spill 这就变成了预览 加上一个 fetch_blob Handle——无用,因为选择工具正是完整列表的目的。提高 这一工具的预算: override: (tool, info) => info.name === "tool_list" ? { ...tool, resultPolicy: { maxTokens: 100_000, overflow: "spill" } } : tool.
  • 上游业务错误可能不会设置 isError. TikTok回报 {"code": 40001, "message": "..."} 作为一个 成功的 MCP的结果;只有mcp级故障(未知工具、传输错误) isError. 任何键控 从 isError -重试,你自己的政策, onEvent 观察者——除非你翻译它们,否则不会看到它们; 这个 withBusinessErrors 下一节中的包装器就是这样做的。

大型平面工作台

相反的形状:数百个普通的工具,每个都诚实地注释。TikTok的平面端点列出了其中的377个, 大约200次读取和130次写入,每个都有 readOnlyHint. 通常需要做两件事:保留写的内容 完全超出了模型的范围(它们通过您自己的工具,使用您自己的授权和审计跟踪), 不要再为每个请求发送200个工具定义了。两者是一体的 override 加上 @reinsjs/brain的 lazyTools():

import { lazyTools } from "@reinsjs/brain"
import { normalizeToolOutput, type Tool, type ToolResult } from "@reinsjs/core"

/** TikTok puts business errors in a successful result: text = {"code": <non-zero>, "message": ...} */
const businessErrorOf = (result: ToolResult): string | undefined => {
  if (result.isError) return undefined
  for (const part of result.content) {
    if (part.type !== "text") continue
    try {
      const body = JSON.parse(part.text) as { code?: unknown; message?: unknown }
      if (typeof body.code === "number" && body.code !== 0)
        return `Upstream error ${body.code}: ${String(body.message ?? "")}`
    } catch {
      // not JSON, not an error envelope
    }
  }
  return undefined
}

/** Keep the original text for the model, but mark it as an error so retries, policies and observers see it */
const withBusinessErrors = (tool: Tool): Tool => ({
  ...tool,
  async execute(input, ctx) {
    const result = normalizeToolOutput(await tool.execute?.(input, ctx))
    const error = businessErrorOf(result)
    return error ? { content: [...result.content, { type: "text", text: error }], isError: true } : result
  },
})

/** reads: on the menu, loaded on demand; everything else: not a tool the model has */
const readOnlyLazy = (tool: Tool, info: McpToolInfo) =>
  info.annotations?.readOnlyHint === true ? withBusinessErrors({ ...tool, lazy: true }) : false

const tiktok = mcpTools({ transport, override: readOnlyLazy })

createAgent({ sockets: [tiktok, lazyTools()] }) // order matters — see below

lazyTools() 从工具绑定中生成它的菜单 之前 它:主机的,加上注册的每个套接字的 早些时候。所以它在 mcpTools(). 另一个订单不是错误,只是无用:MCP工具已经发送进来了 每个转弯都满了,而且 lazyTools() 警告一次,命名他们。有一个较低的层,它有一个原生的位置 延期工具(@reinsjs/lowering-fetch 在Anthropic上)加载工具甚至不会重新加热提示缓存;看到 大脑README的数字。

加载的定义像工具块本身一样受信任(tool_find 结果是 trust: "system") - the 同样的描述已经在模型上展开了。一个200行的菜单大约需要1万个系统代币 提示;它位于缓存的前缀中,并且在运行中不会更改 lazyTools({ summarize }) 可以缩短它。

此配方的可执行版本为 packages/tools-mcp/src/readonly-lazy.recipe.test.ts,包括 次序错了。

已验证的上游行为

请求的历史记录包含 tool_use / tool_result 对于不再在工具表中的工具 被DeepSeek的anthropic协议端点和OpenAI响应协议接受(两者都使用另一个工具) 有一个空的工具表);该模型回答并正确列出了它当前的工具。所以 删除工具不需要在这些上游上创建新的会话。人类自己的终点,到达了 Cloudflare AI Gateway的直通路径,也接受这两种变体(2026-09-15); spikes/l1-deferred-tools P8); 聚合器的Claude端点返回了一个截断的流,这是该代理的已知怪癖,而不是协议 被拒绝。脚本: spikes/mcp-removed-tool-history.

选项

选项默认的意义
transport- - - - - -httpTransport({ url, headers?, auth?, fetch?, requestInit? }) 或者 stdioTransport({ command, args?, env?, cwd?, stderr? })
prefix无加在显示给模型的工具名称前;服务器仍然看到原始名称
callTimeoutMs60 000每 tools/call 超时;过期成为一个错误结果
optionalfalse在 tools/list 运行开始时失败:抛出(默认)或不提供工具并警告一次
override(tool, info)- - - - - -每个工具都重写;返回 false 放下工具
clientInfo{ name: "reins", version }发送MCP握手
warnconsole.warn哪里报告了退化

模型api拒绝的工具名称(^[A-Za-z0-9_-]{1,64}$)被重写(files.read → files_read)用… 一次性警告;电线上使用的是原始名称。

不支持:采样、启发、资源、提示、MCP应用程序和驱动OAuth授权 提示(在您的应用程序中获取令牌并将它们交给您) auth)。

许可证

MIT©2026 newate Limited

版本与发布记录

@reinsjs/tools-mcp 共发布 8 个版本,首次发布于 2026/09/14;过去 12 个月发布了 8 个版本。

版本发布日期距今
0.3.3latest2026/11/021 天前
0.3.22026/10/313 天前
0.3.12026/10/303 天前
0.3.02026/09/221 个月前
0.2.12026/09/151 个月前
0.2.02026/09/151 个月前

关于 @reinsjs/tools-mcp 的常见问题

@reinsjs/tools-mcp 是什么?

缰绳- MCP服务器作为套接字:每次运行一次工具/列表,工具/调用作为工具。执行、注释→风险/审批默认值。在web标准条目上的可流式HTTP,在。/node下的studio

如何安装 @reinsjs/tools-mcp?

在项目目录运行 npm install @reinsjs/tools-mcp,或使用 pnpm add / yarn add / bun add。

@reinsjs/tools-mcp 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@reinsjs/tools-mcp 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@reinsjs/tools-mcp 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@reinsjs/tools-mcp 可以商用吗?

@reinsjs/tools-mcp 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@reinsjs/tools-mcp 还在维护吗?

最新版本 0.3.3 发布于 2026/11/02(1 天前);至今共发布 8 个版本,其中过去 12 个月发布了 8 个;首次发布于 2026/09/14。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。