mcpField
B
businessmcp

businessmcp

远程服务
businessmcp.com商业与营销05 周前更新

关于 businessmcp

businessmcp 的 mcp-gateway 是一组 Node.js 工具,供开发者构建连接第三方 MCP 服务的网关。它提供 URL 安全检查、上游错误分类、避免工具命名冲突和传输回退,但不包含网关或策略层。

主要功能

  1. 01检查公开 HTTP URL 和 IP 安全性
  2. 02分类上游故障
  3. 03生成不冲突的工具名称
  4. 04从 Streamable HTTP 回退到 SSE
  5. 05生成面向用户的故障说明

适用场景

  1. 01验证用户提供的 MCP 服务 URL
  2. 02说明上游连接故障
  3. 03聚合工具时避免名称冲突
  4. 04连接使用不同传输方式的服务

MCP 相关

这些工具帮助开发者构建连接上游 MCP 服务的网关。传输辅助模块使用 @modelcontextprotocol/client;该软件包本身不提供网关聚合或策略管理。

配置

远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。

服务地址https://businessmcp.com/api/mcp
一键复制安装命令
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。

工具

详细介绍

mcp-gateway

用于加固 MCP 网关的实用工具——一种代理他人 MCP 服务的服务端。

这些工具摘自 BusinessMCP 的生产网关。该网关允许工作区连接第三方 MCP 服务,并通过一个受治理的端点重新对外提供这些服务。代理他人的服务意味着要在自己的基础设施上获取用户提供的 URL,而这可能以少数几种方式出大问题。我们运行的就是这套工具。

MIT。两个核心模块没有依赖——只使用两个 Node 内置模块,除此之外别无其他。

npm install @modelcontextprotocol/client   # only if you use the transport helper

1. SSRF 防护,以及绕过测试套件的漏洞

网关会接收用户提供的 URL 并发起请求。没有防护的话,这就成了访问 http://169.254.169.254/ 和获取云凭据的直接途径。

最直观的实现是将主机与保留范围列表进行匹配。我们当时就是这么做的,包括 IPv4 映射的 IPv6 地址:

const mapped = v.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/)   // ← the bug

这个正则表达式在生产环境中从未触发。WHATWG URL 解析器——也就是 new URL(),所有调用方实际使用的解析器——会将 IPv6 主机规范化为其标准形式,而 IPv4 映射地址的标准形式是十六进制:

new URL('http://[::ffff:169.254.169.254]/').hostname
// '::ffff:a9fe:a9fe'

正则表达式匹配的点分形式是调用方无法生成的形式。单元测试之所以通过,是因为它直接使用点分字符串调用了防护函数。测试通过了,但毫无意义:防护函数只通过真实代码路径无法生成的输入进行过测试。

以下地址都能通过检查并发起请求:

URLURL.hostname实际指向
http://[::ffff:169.254.169.254]/::ffff:a9fe:a9fe云元数据
http://[::ffff:127.0.0.1]/::ffff:7f00:1回环地址
http://[::127.0.0.1]/::7f00:1回环地址(IPv4 兼容地址)
http://[64:ff9b::169.254.169.254]/64:ff9b::a9fe:a9fe云元数据(NAT64)
http://[2002:a9fe:a9fe::]/2002:a9fe:a9fe::云元数据(6to4)

在列表中添加更多前缀并不能解决问题。 IPv6 有多种嵌入 IPv4 地址的机制,匹配文本只会让你不断追着一个不受你控制的规范化器补漏洞。这项防护会将地址解码为八个 16 位分组,并进行数值检查;它会解开已知的所有嵌入形式(::ffff: 映射、:: 兼容、64:ff9b:: NAT64、2002:: 6to4),并递归检查其中的 IPv4 地址。无法解码的输入会被默认拒绝——无法解析的地址也就无法保证安全。

import { isPublicHttpUrl, isBlockedIp } from '@businessmcp/mcp-gateway'

await isPublicHttpUrl('http://[::ffff:169.254.169.254]/')  // false
await isPublicHttpUrl('https://api.example.com/mcp')       // true, after resolving every A/AAAA
isBlockedIp('::ffff:a9fe:a9fe')                            // true

isPublicHttpUrl 会解析主机名,并要求返回的每个地址都是公网地址,以尽力防范 DNS 重绑定。这并非万全之策:从检查到套接字连接之间发生重绑定,确实存在 TOCTOU 窗口;要彻底消除此问题,就必须将已解析的 IP 固定到连接上。我们接受这一剩余风险,并认为你应该了解它,而不是等到发现问题时才知道。

还有第二个陷阱需要注意:验证一次主机后,再调用 fetch(url, { redirect: 'follow' }) 就会绕过防护——服务器可以返回 302,将请求重定向到 http://127.0.0.1/,而这个后续请求不会经过检查。应手动跟随重定向,并重新验证每一跳。

2. 对上游故障分类——绝不要将其吞掉

这段代码最初发布的版本会用 catch {} 包裹每次上游调用,并将对应服务从工具列表中移除。身份验证失败、不支持的传输方式、超时、被拦截的主机,以及“服务正常运行,但未返回任何内容”都无法区分——也就是说,这些情况完全不可见。用户只会看到工具悄无声息地消失,产品中没有任何说明。

import { classifyUpstreamError, describeFailure } from '@businessmcp/mcp-gateway'

const failure = classifyUpstreamError(err)
// { reason: 'auth_required' | 'transport_unsupported' | 'timeout'
//         | 'blocked_host' | 'unreachable', detail: string }

describeFailure(failure)  // a sentence you can show a user

这也是诚实判断是否重试的依据:只有类似握手失败的情况才值得尝试其他传输方式。身份验证被拒绝时,换一种传输方式结果也一样。

3. 为工具名称添加命名空间,避免冲突

聚合 N 个服务意味着要为它们的工具添加前缀。MCP 将名称长度限制为 64 个字符,因此添加前缀再截断,可能会让两个不同的上游工具映射到同一个字符串——随后按名称查找时,调用就会被分发到错误的服务。

import { namespacedToolName } from '@businessmcp/mcp-gateway'

namespacedToolName('stripe', 'create_payment_intent')
// 'ext_stripe_create_payment_intent'

需要截断时,会在完整名称后附加一个短哈希值,从而确保两个长名称仍然各不相同。

4. 传输回退

MCP 的 2026-07-28 修订版移除了针对服务的旧版 HTTP+SSE,但你连接的许多服务仍然只支持这种协议。withUpstream 会尝试使用可流式 HTTP,失败后回退到 SSE,并告知你哪种方式生效,以便你保存该方式,下次跳过探测。

import { withUpstream } from '@businessmcp/mcp-gateway'

const res = await withUpstream(
  { url, headers, transport: knownTransport },
  (client) => client.listTools(),
  { onTransportResolved: (t) => save(t) },
)
if (!res.ok) log(describeFailure(res.failure))

需要将 @modelcontextprotocol/client 作为对等依赖项。其他模块则不需要。


坦诚说明局限

  • 这是实用工具,而非框架。 这里没有网关——没有聚合、没有身份验证,也没有策略层。这些功能与连接的存储方式有实质性的耦合,因此,为它们构建一个虚假的抽象还不如不做。
  • 未经外部审计。 这是我们在生产环境中运行的代码。这是应该认真对待它的理由,而不是免于审查的替代品。
  • 特意没有纳入聚合层和检查层,部分原因是它们与我们的数据库耦合,部分原因是它们没有测试覆盖。这里发布的所有内容都来自经过测试的部分。
  • DNS 重绑定 TOCTOU 问题尚未解决,如上所述。

开发

此软件包是从 BusinessMCP 单体仓库中生成的,这些模块目前正在其中投入生产使用。修复应当在该仓库中进行,然后重新生成——只在这里应用的补丁会被覆盖;而手动维护两份安全防护代码,正是导致第 1 节所述绕过问题的原因。

无论如何,欢迎在这里提交问题和报告。

npm install
npx vitest run
npx tsc --noEmit

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。