MCP服务端注册表记录缺失remotes字段导致托管端点不可见

MCP 托管端点能否被发现取决于注册表记录,而非服务器是否在线。
文章以 minia2a 的真实案例说明:注册表客户端不探测服务器,只读取并信任记录,未声明的传输方式等同于不存在。minia2a 同时运营托管端点 https://minia2a.uk/mcp 和本地 stdio 包,但其注册表记录只声明了 packages,remotes 为 null,导致目录仅渲染出「Local stdio」,托管端点对客户端不可见。由于注册表记录不可变,修复方式是另发一条仅含 remotes 的记录。官方注册表 100 条样本中 85 条为仅声明 remotes 的远程专用记录,说明这是常规形态。两项线上验证确认端点正常响应,且注册表已返回两条记录。作者据此给出发布前检查清单。
一个真实案例:托管端点为何在注册表中"消失"
minia2a 运营着一个托管 MCP 端点 https://minia2a.uk/mcp,无需安装、无需 API Key,通过 streamable HTTP 响应 initialize 和 tools/list 请求。同时,他们也发布了一个本地 stdio 包 npx -y minia2a-mcp。
两个端点都是真实存在的。但其中一个在注册表中是"隐形"的。
据开发者博客记录,一个读取官方 MCP 注册表的目录将该服务器渲染为"Local stdio",完全没有远程代理的痕迹。这意味着通过注册表解析该服务的客户端,永远不会知道托管端点的存在——即便它一直在正常响应请求。

问题根源:记录不等于端点
这是一个容易被忽视的关键点:读取注册表的客户端不会探测你的服务器,它读取的是你的"记录"并信任它。如果记录中没有声明某种传输方式,客户端就会表现得好像该传输方式不存在。
minia2a 的记录本身是干净但不完整的。JSON 记录中只声明了 npm 包及 stdio 传输方式,remotes 字段为 null。没有任何内容指向托管端点 URL,因此下游所有客户端都无法发现它。
关键字段缺失在于:注册表清单有一个顶层 remotes 数组,与 packages 平级。每个元素需声明传输类型(streamable-http 或 sse)和绝对 URL。minia2a 的 gating 是按请求级别由客户端发送的,不在清单中硬编码固定 header,因此没有 headers 条目。
MCP(Model Context Protocol) 是由 Anthropic 于 2024 年底提出的开放协议,用于让大语言模型应用以标准化方式连接外部工具与数据源。它定义了若干传输方式(transport),最常见的有两类:stdio——客户端把服务器作为本地子进程启动,通过标准输入输出交换 JSON-RPC 消息,适合随包分发的本地工具;streamable HTTP(及其前身 SSE)——客户端直接向一个 HTTP 端点发送请求,服务器以流式响应,适合无需安装的托管服务。两者在能力上是等价的,但发现路径完全不同:stdio 服务器靠包管理器解析,托管端点则必须有一个可到达的 URL 被显式声明。MCP 注册表正是为后者提供索引,其清单中的传输声明决定了客户端能看到哪种接入方式。
为什么不能直接修改记录?
注册表版本是不可变的:一条记录一旦发布就被冻结。要修改它意味着发布新版本记录——版本号递增是一次新发布,而非编辑。
所以 minia2a 的托管传输方式被放在自己的记录中,完全不包含 packages 数组,只声明 remotes。
如果这看起来像是一种变通做法,数据给出了相反的答案:在从官方注册表抽取的 100 条记录样本中,85 条是仅声明 remotes 的(托管 URL 加无包),只有 15 条是基于包的。远程专用记录是托管端点的常规形态,而非 hack。两条记录存在有其道理:一条描述可安装的包,另一条描述可连接的端点,注册表客户端会选择其支持的传输方式。
85 / 100 注册表记录样本中仅声明远程传输、不含包的占比
注册表不可变设计背后是一套类似内容寻址的发布模型。记录一旦写入即被冻结,任何改动都产生新版本,旧版本仍可被引用。这样做的好处是:客户端或第三方可以缓存某条记录并确信它不会在背后被篡改,供应链审计也更容易——你能精确定位某个版本当时声明了什么。代价是发布前必须把清单字段规划完整,尤其是传输方式的枚举。一旦漏掉 remotes,唯一的补救路径不是「编辑」,而是「再发一条」,因为新旧记录描述的是不同的接口集合,无法用增量更新覆盖。理解这一点,才能理解为什么两条记录并存是常规而非冗余。
验证结果
两项检查均针对线上系统:
-
端点无需任何安装即可响应——POST 请求
https://minia2a.uk/mcp发送initialize方法,返回 HTTP/2 200,content-type 为 application/json,并设置 Mcp-Session-Id,serverInfo响应包含 name 和 version。 -
注册表现在返回该命名空间下的两条记录——stdio 包和远程端点——此前仅显示本地包的目录现在渲染出两个接口。
可复用的经验教训
如果你同时托管 MCP 服务器并作为包发布,你的注册表记录必须同时声明两者。 仅声明 packages 的记录会告诉每一个读取注册表的客户端:你的托管端点不存在——无论它上线多久都无法改变客户端读取到的内容。
一个实用检查清单:
- 如果你在某 URL 响应
initialize,该 URL 就属于remotes。 - 如果你还发布包,保留
packages数组——它们描述的是不同传输方式,而非重复内容。 - 记录不可变:在首次发布之前规划好传输列表,否则就得准备第二条记录。
- 然后像客户端那样读取你自己的记录——因为那才是客户端看到的,而非你的在线时长。
发现(discovery)与可用性(availability)的分离是分布式系统中的经典陷阱:一个服务在网络上完全可用,却可能在任何依赖静态描述的目录中不可见。MCP 注册表采取了「客户端只读记录、不主动探测」的策略,这与浏览器不主动扫描端口、DNS 解析器只查记录而不验证源站在线是同一逻辑。好处是查询快、结果可缓存、不泄露私有端点;代价是记录成为唯一真相来源,记录不完整就等于服务不存在。因此,托管类 MCP 服务上线时的检查项应当包括:「以注册表客户端的视角读一遍自己的记录,能否仅凭它重建出我提供的每一种接入方式?」
一个被忽视的细节
minia2a 的 /.well-known/mcp.json 早已携带指针 {"type":"streamable-http","endpoint":"https://minia2a.uk/mcp"}。但注册表记录没有。两者中,注册表客户端读取的是后者,而后者恰恰是沉默的那个。
对于同时运营托管服务与本地包的 MCP 开发者而言,这个案例值得作为发布前检查项:记录中的传输声明完整性,直接决定了服务能否被注册表生态发现。
相关推荐

Claude Code+Codex多智能体系统实测:缓存率98%背后的真问题
开发者公布Claude Code+Codex多智能体系统7天实测:缓存率98%、输入输出比354:1,作者却卡在组织调度与输出比例,并追问行业基准与成本度量方法。

Cursor SDK 新功能:实时操控运行中的 AI 代理
Cursor SDK 新增 run.steer() 功能,允许开发者在 AI 代理运行中实时注入消息,调整执行方向。子代理遇 steer 时自动转入后台继续任务,为 AI 编程工作流带来更灵活的运行时干预能力。

如何评估AI生成的研究摘要?三个关键维度
AI生成的研究摘要读起来流畅,但事实未必可靠。本文基于Reddit讨论,梳理评估AI研究摘要系统的三个核心维度:来源可追溯性、事实一致性与不确定性处理,并探讨其对深度学习从业者的实践意义。