高级 📋 6 个步骤 第 472 / 473 篇

把 MCP Server 发布到官方 Registry:server.json、所有权校验与 CI 自动化

MCP 官方注册表发布全流程:mcp-publisher 安装、server.json 字段填写、npm mcpName 所有权校验、GitHub OAuth 与 DNS 两条认证路线、dry-run 发布与 GitHub Actions 自动化。

2026.09.25· 22 分钟阅读· 约 2352 字· 📮 MCP Registry / 📦 npm 发布

你照着教程写好了一个能跑的 MCP Server,但它现在只活在你的机器上:同事要用,你得发启动命令;换台电脑,又得讲一遍配置。官方 MCP Registry(registry.modelcontextprotocol.io)解决的就是这个「可发现性」问题:给 Server 写一份 server.json 元数据发布上去,它就会出现在官方目录里,并被 GitHub MCP Registry、VS Code 等下游注册表自动同步。本篇从装 CLI、填清单、过所有权校验,到发布验证、再用 GitHub Actions 自动化,走完全流程。

🎯 适合人群:已经有一个能跑的 MCP Server(本地 stdio 或远程 HTTP 均可,怎么写见站内《从零写一个 MCP Server》)、有 npm 或 PyPI 发布经验、有 GitHub 账号的开发者。全程不需要改 Server 代码本身。

先理解: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

1 官方发布工具,装上并验证
# 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
💡 不想装 Homebrew 也可以直接下对应平台的 tarball 解压,或从源码 make publisher 编译。装完先跑 --help 确认能执行,再往下走。

Step 2:生成并填写 server.json

2 init 起步,逐字段核对

在 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 为例)

3 mcpName 字段,把包和清单钉在一起

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:登录,拿到命名空间

4 io.github 用 OAuth,自有域名用 DNS
# 路线 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格式的私钥
💡 个人开发者和开源项目选路线 A 就够了,零配置成本。企业品牌 Server 建议走路线 B,把 Server 挂在公司域名下,身份可信度更高——域名验证本身就是一道「这是官方出品」的背书。

Step 5:先 dry-run,再正式发布

5 两步发布,发布后回目录页验证
# 干跑:只校验不提交
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 自动化

6 打 tag 自动发布,版本号自动同步

手工发布撑不了几个版本就会忘。把发布挂进 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 schemaserver.json 字段不符合当前 schema。用官方 registry-validator 或 JSON Schema 工具校验后再发
报错包不存在或 mcpName 不匹配包未公开发布,或 package.json 里漏了 / 写错了 mcpName 字段
OCI 镜像形态报 Image not found镜像还没推到镜像仓库,或仓库是私有的。先推公开镜像再发清单
改了 server.json 但目录没变化version 没变。Registry 按版本收录,改内容要同步 bump version 再发布
下游注册表(GitHub / VS Code)还看不到下游同步有延迟。以官方目录页面为准,等一轮同步周期再查
← 返回教程中心