交付一台 MCP 服务器本质是分发问题——Registry 给你一个发现入口,而包类型的选择(六种 registry type 之一)决定了谁真的能装上它。
造好服务器是一件事,让用户看到它是另一件事。MCP Registry——目前最接近 MCP 包索引的东西,而且明确还处在预览期——让你把服务器登记进去;包类型决定了谁能装以及怎么装;而自 2026-07-28 起成为必须实现的 server/discover,意味着已经拿到你 URL 的宿主根本不需要 Registry。这篇文章短,因为话题本身就小;跳过它,用户就找不到你的服务器。
MCP Registry:它现在是什么。
MCP Registry 是已发布服务器的中心索引——名称、描述、代码仓库、版本、它以哪个(或哪些)包的形式交付、它在哪些远程端点上应答,以及它期待哪些环境变量。宿主想让用户"添加一个 MCP 服务器"时,就查询 Registry、做过滤、把得到的启动命令或 HTTP 端点交给自己的传输层。动手对接之前,先看清它的状态行:Registry 自家的 about 页面挂着这样一条横幅——"The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on GitHub."(MCP Registry 目前处于预览阶段;在正式可用之前可能出现破坏性变更或数据重置;遇到问题请到 GitHub 上报。)没有任何已公布的 GA 日期。部署中的服务通过 GET /v0/version 报出的版本是 1.8.1,构建于 2026-08-06。两个 API 前缀同时在线——/v0/ 与 /v0.1/——文档现在把对接方引向后者:"Production applications should consider using /v0.1/ for stability."(生产应用应考虑使用 /v0.1/ 以获得稳定性。)没有 /v1/。失败模式和 npm 一样——没登记的服务器对"只翻 Registry"的宿主完全隐身,而登记了但元数据陈旧的服务器则会因为错误的原因被挑上。
Registry 不是运行时——它不代理工具调用、不承担传输、不签名响应。它只做两件事:发现和元数据。实操构建 MCP 服务器教的一切——工具粒度、resource 与 prompt 之分、annotations——对 Registry 都是不可见的,只留下你写进描述字段里的那些字。所以描述字段才是"选择"真正发生的地方,把它当作营销 slogan 而不是能力句子,是"发布一台没人装的服务器"最快的方法。
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "dev.acme/search",
"description": "Search Acme's docs, then fetch full articles by id.",
"version": "1.4.2",
"repository": { "url": "https://github.com/acme/mcp-search", "source": "github" },
"packages": [
{
"registryType": "npm",
"registryBaseUrl": "https://registry.npmjs.org",
"identifier": "@acme/mcp-search",
"version": "1.4.2",
"runtimeHint": "npx",
"transport": { "type": "stdio" },
"environmentVariables": [
{ "name": "ACME_API_KEY", "description": "Acme API key", "isRequired": true, "isSecret": true }
]
}
],
"remotes": [
{ "type": "streamable-http", "url": "https://mcp.acme.dev/mcp" }
]
}
这份文件叫 server.json,把它推上去的工具叫 mcp-publisher。日常闭环是 init → login → publish;此外还有 validate,在发送之前照 2025-12-11 schema 校验 manifest;status,把某个已发布版本在 active、deprecated、deleted 之间切换;以及 logout,丢掉本地存着的令牌。login 要带一个方法,而方法决定了你被允许往哪个命名空间发布:GitHub OAuth 设备流与 GitHub Actions OIDC 对应 io.github.*,通用 OIDC 对应其他 CI,DNS TXT 记录或托管在 HTTP /.well-known/mcp-registry-auth 的文档对应像 dev.acme/* 这样的域名命名空间,而 none 用于你正在本地测试的 registry 实例。两种域名方法都可以把签名私钥放进 Google KMS 或 Azure Key Vault 而不是落到磁盘上——这正是"一个工程师能用的域名命名空间"与"一个团队能用的域名命名空间"之间的区别。
mcp-publisher init # scaffold server.json, autodetecting the package manager mcp-publisher validate # exhaustive check against the 2025-12-11 schema mcp-publisher login github # or: github-oidc | oidc | dns | http | none mcp-publisher publish # server verifies package ownership + namespace auth mcp-publisher status --status deprecated dev.acme/search 1.4.1
2026 年有两处变更会把原本正常的发布者打断,而且两者的报错都像是鉴权出了问题。往组织命名空间发布,现在要求 GitHub 的 Owner 角色——只是组织成员已经不够了,于是一条跑了一年的发布流水线,会在它背后那个账号不再是 owner 的那一刻开始失败。另外,1.8.1 版本在 DNS 与 HTTP 令牌交换中拒绝 github.io 域名,这断掉了此前大家认领域名命名空间最省事的那条路。发布突然不灵了,先查角色和域名,再去 debug manifest。
包类型:npm、PyPI、OCI——以及另外三种。
三种包类型覆盖了三种现实的交付路径,另有三种给需要它们的生态留着。npm 包(JS/TS 服务器)用 npx 一次性运行,或者 npm i -g 常驻;宿主把它作为子进程拉起来,这条路径默认走 stdio。PyPI 包(Python 服务器)用 pip、pipx 或者 uv 安装;uvx 已经成为 FastMCP 惯用的一次性启动器,扮演的角色相当于 Node 世界里的 npx。OCI 镜像更重,但对运行时无依赖——宿主把它拉下来、跑起来,然后走 Streamable HTTP(而不是 stdio)与之对话,因为把一个容器的 stdio 绑进宿主的工具循环,摩擦比一个网络端点要大得多。取舍并不隐晦:npm 与 PyPI 依赖宿主已经装好可用的 Node 或 Python 运行时;OCI 交付的是一个到处跑法都一致的自包含物件。按受众选——爱好者与小团队吃 npm/PyPI 的亏,企业部署吃 OCI 的亏。
Registry 今天接受的 registryType 全集是 npm、pypi、nuget、cargo、oci 与 mcpb——.NET 与 Rust 服务器都是一等公民,mcpb 则对应 MCP 自己的 bundle 格式。有一处坑值得在浪费一个下午之前先知道:已发布的 2025-12-11 schema 在 registryType 的 examples 里至今没有列出 cargo,尽管校验器是接受它的。信校验器,别信 examples。每一种类型还被钉死在一个受信任的 base URL 上——npm 钉在 registry.npmjs.org、PyPI 钉在 pypi.org、Cargo 钉在 crates.io,OCI 钉在一份包含 Docker Hub、ghcr.io 与 Quay 的短名单上——所以私有镜像源不是一个可发布的来源。
# npm — one-shot, Node runtime npx -y @acme/mcp-search --stdio # PyPI — one-shot, Python runtime uvx acme-mcp-search --stdio # OCI — self-contained image, Streamable HTTP docker run -p 3333:3333 ghcr.io/acme/mcp-search:1.4.2 # registryType values the validator accepts today npm pypi nuget cargo oci mcpb
版本:SemVer 加规范版本钉住。
两个版本号一起走动,把它们混起来正是兼容性 bug 的来源。服务器自身的版本走 SemVer——1.4.2——遵循常规契约:patch 修 bug、minor 加新工具、major 做破坏性 schema 变更。这是 Registry 索引的那个号,也是 mcp-publisher publish 不允许你重复使用的那个号。另一个号是 MCP 协议版本,一个以日期命名的规范修订版——当前是 2026-07-28,它接替了 2025-11-25。从那一版起,这个号不再是在 initialize 握手里一次谈定的,因为握手已经没有了:版本随每一次请求一起走,而想在对接之前就知道某台服务器说什么版本的客户端,会去调 server/discover——每台服务器都必须实现它,它会回答一份受支持版本的列表。两边都必须能处理"宿主比服务器新"和"服务器比宿主新"这两种情况而不至于崩溃。一台把自己钉死在某一个规范版本上的服务器,会让每一次规范升级都变成对客户端的强制升级——从 HTTP+SSE 迁到 Streamable HTTP 拖了几个月,就是因为服务器钉得太窄。
而把这两个号连起来的,不是 Registry。server.json 里压根没有协议版本字段:这份 manifest 描述包、传输与环境,对"服务器支持哪些规范修订版"只字不提。因此宿主没法在安装之前按规范兼容性过滤 Registry——它要么在第一次请求时才知道,要么连上之后调一次 server/discover。所以请把你支持的修订版写进描述字段或仓库 README,因为眼下那是潜在用户唯一能读到它的地方。
运行时发现如今已经存在,而路线图要去的是别的方向。
已经知道服务器 URL 的宿主——无论是企业自托、本地运行,还是被收藏过的——如今不必再通过 Registry 才能知道这台服务器能做什么。2026-07-28 修订版把 server/discover 定为必须实现,它会返回 supportedVersions、capabilities、可选的 instructions,以及缓存提示 ttlMs 与 cacheScope。这就是运行时发现路径——而它落在协议内部,并不是协议旁边某个 well-known 地址上。这一通用模式在能力发现一文里已有交代。
路线图接下来真正排在前面的,是另一个问题:不是"宿主如何得知服务器提供了什么",而是"宿主如何避免吞下一份它负担不起的清单"。Core Primitives 工作组的职责范围里写着渐进式发现——让客户端按需逐步了解服务器的工具与资源,而不是一上来就把整份列表全部取走——并明确与那项引入 ttlMs 和 cacheScope 的缓存工作绑定,还要进一步扩展到 ETag。面向工具列表的能力范围收敛,则被列为无状态修订版的后续工作。把你的服务器元数据设计成宿主可以分片获取的样子,并让一次列表结果值得被缓存;协议正沿着这条轴线前进。