跳到主要内容

MCP Registry — 架构与原理

30 秒导读: MCP Registry 是 MCP 服务器的「应用商店」的后端目录。它本身不存任何代码或二进制,只存一份描述「某个 MCP 服务器叫什么、去哪下载、怎么跑」的元数据(server.json)。它最核心、也最难的一件事是:怎么保证「io.github.alice/weather」这个名字只能被真正的 alice 发布——答案是一整套「证明你拥有这个命名空间」的密码学握手。

1. 这是什么(零基础也能懂)

一句话定义: MCP Registry 是一个 Go 写的 HTTP 服务 + PostgreSQL,维护一份公开、带版本、可增量同步的 MCP 服务器清单。

先理解背景:MCP 是什么、为什么需要一个注册表。 MCP(Model Context Protocol)是让 AI 客户端(如 Claude Desktop、各类 IDE 助手)接入「工具/数据源服务器」的协议。社区里有成千上万个这样的 MCP 服务器(查天气的、连数据库的、读 GitHub 的……)。问题来了:客户端怎么发现它们?怎么知道每个该怎么装、怎么跑? 这就是注册表要解决的。

关键定位:它是 metaregistry(元注册表),不是包仓库。 这是理解整个项目的第一把钥匙。

存什么例子
包仓库(npm / PyPI / Docker Hub)真实代码 / 二进制weather-mcp 的实际 tar 包
MCP Registry(本项目)指向包的元数据「weather-server v1.2.0 在 npm 上叫 weather-mcp

这个区分写在设计文档里(docs/design/ecosystem-vision.md「Registry vs Package Registry」一节):registry 只说「东西在哪」,真正的代码仍住在 npm/PyPI/Docker。

给谁用:

  • 服务器作者——用一个叫 mcp-publisher 的 CLI 把自己的 server.json 发布上来。
  • 客户端 / 子注册表(subregistry)——通过只读 API 拉清单。Smithery、PulseMCP 这类「子注册表」会定期 ETL 官方 registry,再加自己的评分/策展。

用起来什么样(作者视角的最小流程):

# 1. 生成一份 server.json 模板
mcp-publisher init

# 2. 登录(走 GitHub OAuth,证明你是 github.com/alice)
mcp-publisher login github

# 3. 发布
mcp-publisher publish
# → Publishing to https://registry.modelcontextprotocol.io...
# → ✓ Successfully published
# → ✓ Server io.github.alice/weather version 1.0.0

一句话直觉/类比: 把它想成 npm registry 的「元数据层」+ DNS 的「域名所有权证明」的结合体。名字用反向 DNS(io.github.alice/weather),而「你凭什么能用这个名字」靠的是和 DNS-01 证书挑战同源的思路:你得证明你控制那个域名/那个 GitHub 账号

本节到此——你现在应该能对别人讲清楚「MCP Registry 是个只存元数据、用域名所有权防冒名的服务器目录」。

2. 顶层全景(它大概怎么转)

这一节给你「大盘」:有哪些部件、一次发布怎么流过它们。

2.1 一张图:发布主线

作者机器 Registry 服务 (Go + Postgres)
┌──────────┐ ┌───────────────────────────────────────┐
│mcp- │ ① 换 token │ auth handlers (github/dns/http/oidc) │
│publisher │ ───────────────▶ │ 「证明你拥有命名空间」→ 签发 5 分钟 JWT │
│ (CLI) │ ◀─────────────── │ JWT 里带 permissions: io.github.alice/*│
└────┬─────┘ Registry JWT └───────────────────────────────────────┘

│ ② POST /v0/publish (Bearer JWT + server.json)

┌───────────────────────────────────────────────────────────────────────┐
│ publish handler → registry service (一个大事务) │
│ │
│ (a) 验 JWT (b) 权限匹配 (c) schema 校验 │
│ jwtManager HasPermission ValidateServerJSON │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ (d) 包归属校验 → (e) 取锁 → (f) 版本检查 → (g) 算 latest → 落库 │
│ 去 npm/OCI advisory 重复? 上限? 比版本号 │
│ 反查 mcpName lock CompareVersions │
└───────────────────────────────────────────────────────────────────────┘


┌──────────┐ 只读 + CDN 缓存
│ Postgres │ ◀───────────────── 客户端 / 子注册表 GET /v0/servers
└──────────┘

怎么读这张图: 上半是「拿令牌」(第 1 章),下半是「带令牌发布」(第 2 章)。(d) 包归属校验是第 3 章,(f)(g) 版本逻辑是第 4 章。

2.2 部件一句话职责

部件干什么在哪个文件
publisher CLI作者侧:init / login / publishcmd/publisher/
auth handlers把外部身份(GitHub/DNS/HTTP)换成 Registry JWTinternal/api/handlers/v0/auth/
JWTManager用 Ed25519 签发/校验 5 分钟短令牌,做权限匹配internal/auth/jwt.go
publish/edit handlersHTTP 层:验令牌、查权限、调 serviceinternal/api/handlers/v0/publish.go
registry service核心业务:事务、版本检查、latest 重算internal/service/registry_service.go
validatorsschema 校验 + 「包归属」反查 npm/PyPI/OCI…internal/validators/
Database (Postgres)版本化存储、advisory lock、游标分页internal/database/postgres.go
model / api typesserver.json 的数据结构pkg/model/pkg/api/v0/

2.3 主线走一遍(高层,不进代码)

  1. 作者 login:CLI 拿到 GitHub OAuth token,POST 给 /v0/auth/github-at。registry 用这个 token 去问 GitHub「你是谁、属于哪些 org」,据此签发一个只对 io.github.<你>/* 有发布权的 5 分钟 JWT
  2. 作者 publish:CLI 带着 JWT + server.json POST 到 /v0/publish
  3. registry 依次:验 JWT 签名 → 令牌权限能否覆盖你要发的名字 → schema 合法吗 → (可选)去 npm/OCI 反查这个包是否声明属于这个 server → 版本不重复/没超上限 → 跟现有版本比大小决定是不是新的 latest → 在一个事务里落库。
  4. 任何人 GET /v0/servers:从只读端点(前面挂 CDN)拉清单,可按 updated_since 做增量同步。

看懂这条主线,就可以按下面的阅读地图下钻。

3. 阅读地图(建议顺序)

这个项目的「价值」和「难点」高度集中在命名空间所有权发布管线上,建议按序读:

  1. 01-naming-and-ownership.md — 先搞懂名字规则和三种所有权证明握手。这是整个项目最独特的部分,务必先读。
  2. 02-publish-pipeline.md — 一次 publish 在服务端被分成哪几个 phase、每步在防什么。
  3. 03-registry-ownership-validation.md — 「双向绑定」:不仅你要证明拥有名字,包也要声明它属于这个名字。
  4. 04-versioning-and-latest.md — 版本比较的三规则、is_latest 重算、并发锁。
  5. 05-security-and-boundaries.md — SSRF 防护等安全设计、巧妙之处、边界、横向对比。

4. 代码地图(导航索引)

主题文件关键符号
server.json 数据模型pkg/api/v0/types.goServerJSONServerResponseRegistryExtensions
包/transport/输入模型pkg/model/types.goPackageTransportInputArgument
JWT 签发/校验/权限匹配internal/auth/jwt.goJWTManagerGenerateTokenResponseHasPermissionisResourceMatch
命名空间黑名单internal/auth/blocks.goBlockedNamespaces
发布事务核心internal/service/registry_service.gocreateServerInTransactionrecalculateLatest
版本比较internal/service/versioning.goCompareVersionsIsSemanticVersion
publish HTTP 层internal/api/handlers/v0/publish.goRegisterPublishEndpointbuildPermissionErrorMessage
签名验证 / key 解析internal/api/handlers/v0/auth/common.goCoreAuthHandler.ExchangeTokenVerifySignatureWithKeysBuildPermissions
GitHub / DNS / HTTP 握手internal/api/handlers/v0/auth/{github_at,dns,http}.goGitHubHandlerDNSAuthHandlerHTTPAuthHandler
包归属反查internal/validators/package.gointernal/validators/registries/ValidatePackageValidateNPMValidateOCI
advisory lockinternal/database/postgres.goAcquirePublishLockhashServerName