你照着教程写好了一个能跑的 MCP Server,但它现在只活在你的机器上:同事要用,你得发启动命令;换台电脑,又得讲一遍配置。官方 MCP Registry(registry.modelcontextprotocol.io)解决的就是这个「可发现性」问题:给 Server 写一份 server.json 元数据发布上去,它就会出现在官方目录里,并被 GitHub MCP Registry、VS Code 等下游注册表自动同步。本篇从装 CLI、填清单、过所有权校验,到发布验证、再用 GitHub Actions 自动化,走完全流程。
先理解:Registry 的三层模型
动键盘之前先建立正确的预期。官方 Registry 是个元数据目录,不是代码托管或分发渠道——它不存你的代码,只存「你的 Server 叫什么、从哪装、怎么连」。
| 部署形态 | Registry 里登记什么 | 用户怎么用 |
|---|---|---|
| Package(本地包) | npm / PyPI / NuGet / OCI 镜像的标识与版本 | 客户端按元数据从对应包管理器拉取,本地运行 |
| Remote(远程服务) | 一个公开可访问的 URL(你的 Server 托管为 Web 服务) | 客户端直连你的端点 |
| Hybrid(混合) | 两者都登记 | 本地能跑的本地跑,重资源场景连远程 |
还有一层容易忽略的设计:name 字段使用反向 DNS 命名空间——io.github.你的用户名/服务名 或 com.你的域名/服务名。命名空间决定你用什么方式认证:io.github 开头的用 GitHub OAuth 登录即可;自定义域名命名空间需要证明域名所有权(DNS 记录或 HTTP 文件验证)。
Step 1:安装 mcp-publisher CLI
# macOS / Linux(Homebrew)
brew install mcp-publisher
# Windows(PowerShell,下载预编译二进制)
$arch = "amd64" # ARM 机器改为 arm64
Invoke-WebRequest -Uri "https://github.com/modelcontextprotocol/registry/releases/download/v1.0.0/mcp-publisher_1.0.0_windows_$arch.tar.gz" -OutFile mcp-publisher.tar.gz
tar xf mcp-publisher.tar.gz
# 验证
mcp-publisher --help
make publisher 编译。装完先跑 --help 确认能执行,再往下走。Step 2:生成并填写 server.json
在 Server 项目根目录执行 mcp-publisher init,会自动探测项目生成模板。以一个发布到 npm 的 stdio Server 为例,填完关键字段长这样:
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
"name": "io.github.你的用户名/your-mcp-server",
"description": "一句话说清这个 Server 提供什么工具、给谁用",
"version": "1.0.0",
"packages": [
{
"registryType": "npm",
"identifier": "your-mcp-server",
"version": "1.0.0",
"transport": { "type": "stdio" }
}
],
"repository": {
"url": "https://github.com/你的用户名/your-mcp-server",
"source": "github"
}
}
几个必须核对的点:version 与你 npm 包的版本保持一致;远程 Server 则把 packages 换成 remotes 数组、填你的公开 URL;name 大小写敏感,发布后就是它在目录里的身份证号。
别把不该公开的东西写进清单。server.json 是公开发布的元数据,环境变量的名字可以写(用户需要知道配什么),但密钥值、内网地址、未公开的接口细节都不要出现。
Step 3:过所有权校验(npm 为例)
Registry 会去 npm 拉你的包,校验包里声明的 mcpName 与 server.json 的 name 一致——这是防止有人冒充你的 Server 的关键机制。给 package.json 加一个字段:
{
"name": "your-mcp-server",
"version": "1.0.0",
"mcpName": "io.github.你的用户名/your-mcp-server"
}
# 然后发布或更新包
npm publish --access public
校验逻辑:Registry 请求 registry.npmjs.org/your-mcp-server,读取 mcpName,与清单比对——字段缺失或不匹配,发布直接失败。PyPI 包同理,在包的 README 里加一条 HTML 注释形式的 mcp-name 声明;NuGet 也是走 README 注释。
包必须先公开发布且可被拉取。Registry 只做元数据,npm 上还没有这个包(或是私有包),发布必然失败。顺序永远是:先发包,再发清单。
Step 4:登录,拿到命名空间
# 路线 A:GitHub OAuth(对应 io.github.用户名/* 命名空间)
mcp-publisher login github
# 会打开浏览器完成 GitHub 授权,回来即拿到你的命名空间
# 路线 B:自有域名(对应 com.你的域名/* 命名空间)
# 生成 Ed25519 密钥对,公钥按官方要求放进 DNS TXT 记录或 .well-known 文件
openssl genpkey -algorithm Ed25519 -out key.pem
mcp-publisher login dns --domain=你的域名.com --private-key=HEX格式的私钥
Step 5:先 dry-run,再正式发布
# 干跑:只校验不提交
mcp-publisher publish --dry-run
# 正式发布
mcp-publisher publish
# 验证:打开官方目录搜索你的 Server
# https://registry.modelcontextprotocol.io/servers/io.github.你的用户名/your-mcp-server
发布成功后,你的 Server 会出现在 registry.modelcontextprotocol.io,并作为上游数据源被 GitHub MCP Registry、VS Code 等下游注册表逐步同步——这也是「发一次、多处可见」的价值所在。
dry-run 不是可省的形式主义。它会在提交前拦下 schema 错误、命名空间不匹配、版本不一致这类最常见的问题。直接 publish 失败的报错信息远不如 dry-run 清晰,先用干跑把低级错误清干净。
Step 6:交给 GitHub Actions 自动化
手工发布撑不了几个版本就会忘。把发布挂进 CI:打 v* 标签时自动发 npm、同步版本号、再发布到 Registry。
# .github/workflows/publish.yml 关键步骤(简化示意)
on:
push:
tags: ["v*"]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 版本号写入 server.json(取自 tag)
run: |
VERSION="${GITHUB_REF_NAME#v}"
jq --arg v "$VERSION" '.version = $v |
(.packages[] | select(.registryType=="npm") | .version) = $v' \
server.json > tmp.json && mv tmp.json server.json
- name: npm 发布
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: MCP Registry 发布
run: |
mcp-publisher login github # CI 内用 token 方式认证
mcp-publisher publish
env:
MCP_PRIVATE_KEY: ${{ secrets.MCP_PRIVATE_KEY }}
需要配置两个 secret:NPM_TOKEN(npm 发布令牌)与 Registry 认证凭据。此后你的发布流程收敛成一条命令:npm version patch && git push --tags。
CI 里认证凭据只进 secrets,绝不落仓库。GitHub OAuth 的登录态不适合放 CI,自动化场景按官方文档使用 token / 密钥方式认证,并把对应 secret 配置在仓库设置里。私钥文件泄漏 = 你的命名空间被人接管。
发布之后:更新节奏与远程形态补充
发布不是终点,后面还有两类日常动作要想清楚。其一是更新节奏:Registry 按版本收录,你每次改了 Server(哪怕只是 README),都要同步 bump server.json 与包版本再发一次,否则目录里永远是你上次发布的那一版。建议把「改代码 → 发包 → 发清单」固化成一条流水线(Step 6 的 CI 就是干这个的),人只负责打 tag。其二是远程形态:如果你的 Server 是托管为 Web 服务的 Remote 形态,server.json 里不写 packages,改写 remotes 数组填公开 URL;认证方式、传输协议(streamable HTTP 等)也按官方 schema 对应字段登记。很多团队两条路都留:本地包给个人开发者试玩,远程端点给生产环境调用——这就是 Hybrid 形态,一份清单同时登记两种入口。
还有一个容易被问的问题:发布后能撤销吗?官方 Registry 提供 unfublish 通道(用同一身份认证执行),但下游注册表的同步有延迟,已同步出去的记录不受你控制。所以发布前把描述、仓库地址、环境变量名单逐项过一遍——元数据一旦出去,就按「公开信息」管理,别指望能彻底收回。
收尾前做一轮发布自查三问:一问 name 与 version 是否处处一致(server.json、package.json / pyproject.toml、Git tag 三方对齐,谁不一致发布就卡谁);二问描述是否说清了「这个 Server 提供哪些工具、适合什么场景」——目录页是别人决定要不要装你的 Server 的依据,一句模糊的描述等于自己劝退用户;三问环境变量名单是否完整且只含变量名不含密钥值,用户拿到清单就能配好环境,才算一份合格的元数据。
常见问题速查
| 你遇到的现象 | 大概率原因 & 解决 |
|---|---|
| 报错 Name not in your namespace | 登录账号与 name 前缀不匹配。io.github 前缀用对应 GitHub 账号 OAuth;域名前缀完成 DNS 验证 |
| 报错 Invalid schema | server.json 字段不符合当前 schema。用官方 registry-validator 或 JSON Schema 工具校验后再发 |
| 报错包不存在或 mcpName 不匹配 | 包未公开发布,或 package.json 里漏了 / 写错了 mcpName 字段 |
| OCI 镜像形态报 Image not found | 镜像还没推到镜像仓库,或仓库是私有的。先推公开镜像再发清单 |
| 改了 server.json 但目录没变化 | version 没变。Registry 按版本收录,改内容要同步 bump version 再发布 |
| 下游注册表(GitHub / VS Code)还看不到 | 下游同步有延迟。以官方目录页面为准,等一轮同步周期再查 |