跳到主要内容
查看所有作者

Btrfs scrub 实用指南:启动、限速、查看进度和速度

· 阅读需 11 分钟

Btrfs 的 scrub 是一个很适合定期运行的在线校验工具。它会遍历 Btrfs 文件系统中的数据和元数据,检查校验和错误、读错误、部分元数据头错误和超级块错误。

如果文件系统使用了有冗余副本的 profile,比如 RAID1、RAID10 或 DUP,读写挂载状态下的 scrub 在发现坏副本时,可以尝试从好的副本修复。它不是 fsck,也不能替代 btrfs check;它更像是一次在线的数据完整性巡检。

这篇文章记录一套实用流程:怎么找可 scrub 的挂载点,怎么启动,怎么降低 I/O 影响,怎么查看状态,以及在旧版工具不显示 Rate / ETA / 百分比时,怎么自己算速度和进度。

scrub 的对象是什么?

btrfs scrub 不是按目录工作的。

你传给它的路径可以是一个 Btrfs 文件系统里的路径,但 scrub 的对象是这个路径所属的 Btrfs 文件系统,或者指定的设备。

例如:

sudo btrfs scrub start /volume3

如果 /volume3 是一个 Btrfs 文件系统的挂载点,那么它会 scrub 这个文件系统。它不会只校验 /volume3 下某个目录。

如果同一个 Btrfs 文件系统有多个子卷分别挂载,比如 //home/var/lib/docker,对其中任意一个挂载点执行 scrub,本质上仍然是在 scrub 同一个 Btrfs 文件系统。

如何找可以 scrub 的挂载点

findmnt 的系统上,最简单:

findmnt -t btrfs

只看挂载点路径:

findmnt -rn -t btrfs -o TARGET

如果系统没有 findmnt,可以直接读 /proc/mounts

awk '$3=="btrfs"{print $2}' /proc/mounts

看更完整的信息:

awk '$3=="btrfs"{print $1, $2, $4}' /proc/mounts

也可以用传统 mount 输出粗筛:

mount | grep ' type btrfs '

拿到挂载点后,通常对每个实际 Btrfs 文件系统选一个代表挂载点运行 scrub 即可。

启动 scrub

后台启动:

sudo btrfs scrub start /volume3

前台启动,直到完成才返回:

sudo btrfs scrub start -B /volume3

-B 的意思是不要后台运行。它适合你想在当前终端等待结果的场景。

如果想在前台运行,并在结束时显示每个设备的统计信息:

sudo btrfs scrub start -Bd /volume3

注意这里的 -dstart 子命令的参数,并且只在 -B 时有效。它表示完成后按设备打印统计。

后台启动后,常用状态命令是:

sudo btrfs scrub status /volume3

按设备查看状态:

sudo btrfs scrub status -d /volume3

status -dstart -d 不一样。status -d 是查看状态时按设备显示,可以单独使用。

暂停、取消和恢复

取消正在运行的 scrub:

sudo btrfs scrub cancel /volume3

恢复上次取消或中断的 scrub:

sudo btrfs scrub resume /volume3

查看状态:

sudo btrfs scrub status /volume3

Btrfs 会把 scrub 状态记录在 /var/lib/btrfs/ 下,文件名通常包含文件系统 UUID。官方文档说明状态文件大约每 5 秒更新一次。

降低 I/O 影响:ionice 优先级

旧版 btrfs scrub start --help 常见参数是:

usage: btrfs scrub start [-BdqrRf] [-c ioprio_class -n ioprio_classdata] <path>|<device>

其中:

-c set ioprio class
-n set ioprio classdata

这相当于内置了类似 ionice 的 I/O 优先级控制。

最保守的后台运行方式:

sudo btrfs scrub start -c 3 /volume3

含义:

-c 3 = idle I/O class

idle 类不需要 -n。它表示只有系统 I/O 比较空闲时才积极执行。

如果想用普通类里的最低优先级:

sudo btrfs scrub start -c 2 -n 7 /volume3

含义:

-c 2 = best-effort
-n 7 = best-effort 里的最低优先级

也可以用外部 ionice 包一层:

sudo ionice -c3 btrfs scrub start /volume3

或者:

sudo ionice -c2 -n7 btrfs scrub start /volume3

需要注意:ionice-c/-n 控制的是 I/O 调度优先级,不是固定 MB/s 限速。并且在现代 Linux 常见 I/O scheduler 下,它不一定都有效。Btrfs 官方文档也提醒,相关优先级机制依赖 I/O scheduler,常见的 mq-deadline 并不一定按预期支持这种优先级控制。

真正按速度限速

较新的 btrfs-progs 支持更直接的 scrub 限速方式,但这里的“新”要看具体版本:

  • btrfs scrub limitbtrfs-progs 6.6.3 引入,用于查看或设置每设备 scrub 限速。
  • btrfs scrub start --limitbtrfs-progs 6.13 引入,用于本次 scrub 运行期间临时设置吞吐上限。

所以 btrfs-progs v6.2 没有 scrub limitscrub start --limit 是正常的,即使发行版或 NAS 系统本身已经是最新版本。

如果 btrfs scrub start --help 里有 --limit,可以这样:

sudo btrfs scrub start --limit 100m /volume3

如果有 btrfs scrub limit 子命令,可以查看或设置每个设备的限制:

sudo btrfs scrub limit /volume3
sudo btrfs scrub limit --all --limit 100m /volume3

Linux 5.14 之后,还可以通过 Btrfs 的 sysfs 文件设置每设备 scrub 速度上限:

echo 100m | sudo tee /sys/fs/btrfs/<FSID>/devinfo/<DEVID>/scrub_speed_max

这个设置不是持久化配置,卸载文件系统后会消失。

如果用户态工具还是 btrfs-progs 6.2 这类旧版本,但内核足够新,可以先检查 sysfs 入口是否存在:

ls /sys/fs/btrfs/*/devinfo/*/scrub_speed_max

如果存在,可以绕过 btrfs scrub limit 命令,直接写入该文件。写入前需要确认对应的 FSID 和 DEVID 属于目标文件系统。

另一种通用做法是使用 cgroup v2 / systemd 的 I/O 限制,例如官方文档给出的形式类似:

sudo systemd-run -p "IOReadBandwidthMax=/dev/sdx 10M" btrfs scrub start -B /

这种方式比 -c 3 更接近真正限速,但配置复杂度更高,而且需要确认实际底层设备路径。

查看状态:新版本的理想输出

新版本 btrfs scrub status 可能会直接显示这些字段:

Status: running
Duration: 0:00:05
Time left: 0:00:05
ETA: Wed Apr 10 12:35:01 2023
Total to scrub: 28.32GiB
Bytes scrubbed: 13.76GiB (48.59%)
Rate: 2.75GiB/s
Error summary: no errors found

这种情况下很简单:

  • 速度看 Rate
  • 进度看 Bytes scrubbed 和百分比
  • 剩余时间看 Time leftETA
  • 错误看 Error summary

实时刷新:

watch -n 5 'sudo btrfs scrub status -d /volume3'

如果没有 watch

while true; do
clear
date
sudo btrfs scrub status -d /volume3
sleep 5
done

旧版本只显示已 scrub 字节数怎么办?

有些 NAS 或旧版 btrfs-progs 的输出比较简略,例如:

scrub status for d1a06ecc-e68a-46c5-9741-7bd9ab592c4b
scrub device /dev/mapper/cachedev_0 (id 1) status
scrub started at Sun Jun 28 09:15:15 2026, running for 00:00:56
total bytes scrubbed: 22.22GiB with 0 errors

这种输出没有 Rate,也没有总进度。平均速度可以手算:

平均速度 = total bytes scrubbed / 已运行秒数

上面的例子:

22.22GiB / 56s ≈ 406MiB/s

但这只是从启动到现在的平均速度,不是瞬时速度。

如果支持 -R,可以查看原始状态:

sudo btrfs scrub status -R /volume3

可能会得到类似:

data_extents_scrubbed: 2809542
tree_extents_scrubbed: 55582
data_bytes_scrubbed: 184036638720
tree_bytes_scrubbed: 910655488
read_errors: 0
csum_errors: 0
verify_errors: 0
uncorrectable_errors: 0
corrected_errors: 0
last_physical: 186868826112

这里可以用:

已 scrub 字节 = data_bytes_scrubbed + tree_bytes_scrubbed
实时速度 = 两次已 scrub 字节差值 / 间隔秒数

last_physical 表示当前扫到的物理位置。它可以用来粗略估算进度,但不是一个完美的百分比分母。

一个简易 scrubtop 脚本

下面这个脚本每隔 5 秒调用一次 btrfs scrub status -R,根据两次采样的字节差计算实时速度。

#!/bin/sh
# usage: scrubtop /volume3

path="${1:-/volume3}"
interval="${INTERVAL:-5}"

prev_bytes=""
prev_time=""

while true; do
out="$(sudo btrfs scrub status -R "$path" 2>/dev/null)"
now="$(date +%s)"

data="$(printf '%s\n' "$out" | awk -F': ' '/data_bytes_scrubbed/ {print $2}')"
tree="$(printf '%s\n' "$out" | awk -F': ' '/tree_bytes_scrubbed/ {print $2}')"
errors="$(printf '%s\n' "$out" | awk -F': ' '/uncorrectable_errors/ {print $2}')"
read_errors="$(printf '%s\n' "$out" | awk -F': ' '/read_errors/ {print $2}')"
csum_errors="$(printf '%s\n' "$out" | awk -F': ' '/csum_errors/ {print $2}')"
last_physical="$(printf '%s\n' "$out" | awk -F': ' '/last_physical/ {print $2}')"

bytes=$((data + tree))

clear
date
printf 'Path: %s\n' "$path"
printf 'Scrubbed: %.2f GiB\n' "$(awk "BEGIN {print $bytes/1024/1024/1024}")"
printf 'Last physical: %.2f GiB\n' "$(awk "BEGIN {print $last_physical/1024/1024/1024}")"

if [ -n "$prev_bytes" ]; then
dt=$((now - prev_time))
db=$((bytes - prev_bytes))
if [ "$dt" -gt 0 ]; then
printf 'Rate: %.2f MiB/s\n' "$(awk "BEGIN {print $db/$dt/1024/1024}")"
fi
else
printf 'Rate: collecting baseline...\n'
fi

printf 'Errors: read=%s csum=%s uncorrectable=%s\n' "$read_errors" "$csum_errors" "$errors"
printf '\nRaw status:\n%s\n' "$out"

prev_bytes="$bytes"
prev_time="$now"
sleep "$interval"
done

保存为 scrubtop 后运行:

chmod +x scrubtop
./scrubtop /volume3

如果想改刷新间隔:

INTERVAL=10 ./scrubtop /volume3

进度百分比怎么估算?

精确进度依赖工具是否能给出 Total to scrub。如果 btrfs scrub status 没有这个字段,只有 -R 的 raw 输出,那么只能估算。

常见估算口径有两个:

1. 用 last_physical / 设备大小

查设备大小:

sudo blockdev --getsize64 /dev/mapper/cachedev_0

估算:

进度 ≈ last_physical / 设备大小

这个口径适合单设备、物理布局相对直观的场景,但不等于 Btrfs 逻辑层面的真实待 scrub 数据量。

2. 用 Btrfs 已分配空间做分母

查看 Btrfs 空间使用:

sudo btrfs filesystem usage -b /volume3

估算:

进度 ≈ 已 scrub 字节 / 已分配空间

这个口径更贴近 scrub 扫描已分配数据/元数据块的事实,但在 RAID、DUP、压缩、快照、多设备场景下仍然只是估算。

结论是:如果工具不显示 Total to scrub,就不要把估算百分比当成精确进度。速度和错误计数更可靠。

错误字段怎么看?

常见字段:

read_errors
csum_errors
verify_errors
super_errors
uncorrectable_errors
corrected_errors
unverified_errors

重点关注:

  • corrected_errors:发现坏块,并且已经从好副本修复。
  • uncorrectable_errors:发现错误,但没有可用好副本修复,需要进一步处理。
  • csum_errors:数据校验和错误。
  • read_errors:底层读失败,可能涉及磁盘、线缆、控制器或阵列层问题。
  • verify_errors:元数据块头校验类错误。

如果出现 uncorrectable_errors,不要只重跑 scrub。应该先保存状态输出,再检查内核日志、设备 SMART、Btrfs device stats 和备份状态。

dmesg | grep -iE 'btrfs|i/o error|checksum|read error'
sudo btrfs device stats /volume3

推荐实践

日常后台低影响运行:

sudo btrfs scrub start -c 3 /volume3

查看状态:

sudo btrfs scrub status -d /volume3

旧版工具查看 raw 状态:

sudo btrfs scrub status -R /volume3

新版工具如果支持真正限速:

sudo btrfs scrub start --limit 100m /volume3

或:

sudo btrfs scrub limit --all --limit 100m /volume3
sudo btrfs scrub start /volume3

如果要在维护窗口里跑:

sudo btrfs scrub start -c 3 /volume3

到时间后:

sudo btrfs scrub cancel /volume3

下次继续:

sudo btrfs scrub resume /volume3

小结

btrfs scrub 的关键点可以压缩成几句话:

  • scrub 面向 Btrfs 文件系统或设备,不面向目录。
  • 默认后台运行;-B 才是前台等待完成。
  • -c 3 / ionice -c3 是降低 I/O 优先级,不是真正 MB/s 限速。
  • btrfs scrub limit 需要 btrfs-progs 6.6.3 及以上;scrub start --limit 需要 btrfs-progs 6.13 及以上。
  • Linux 5.14 及以上内核可能支持通过 scrub_speed_max 做每设备限速,即使旧版 btrfs-progs 没有对应子命令。
  • 新版 status 可能直接显示 RateETA 和百分比。
  • 旧版可用 status -Rdata_bytes_scrubbed + tree_bytes_scrubbed 计算实时速度。
  • 没有 Total to scrub 时,进度百分比只能估算,不应当当作精确值。

参考资料

  • Btrfs scrub documentation
  • Btrfs progs changelog
  • 本文示例来源于 2026-06-28 的一次实际排查记录:旧版 btrfs-progs 只显示 total bytes scrubbed,但 status -R 能提供 data_bytes_scrubbedtree_bytes_scrubbedlast_physical

从零认识 rootfs:它到底是文件系统、分区,还是 Linux 里的角色?

· 阅读需 17 分钟

学习 Linux、嵌入式系统、Docker 或容器时,经常会遇到一个词:rootfs

它看起来像某种文件系统格式,但又不像 ext4xfsbtrfs 那样能被 mkfs.ext4 之类的命令创建。它有时又和分区、镜像、容器、initramfs、overlayfs 一起出现,让人很容易混淆:

rootfs 到底是什么?是文件系统?是分区格式?还是磁盘上的某个区域?

最直接的答案是:

rootfs 是 root filesystem 的缩写,指 Linux 系统中作为根目录 / 使用的那个文件系统。它是一个文件系统/挂载命名空间层面的角色,不是磁盘角色,也不是分区格式。

这句话看起来简单,但要真正理解它,需要先从 Linux 的目录树讲起。

Linux 为什么需要 rootfs?

在 Linux/Unix 系统里,所有路径都挂在同一棵目录树下面。这棵树的根是 /

比如:

/
├── bin
├── boot
├── dev
├── etc
├── home
├── proc
├── sys
├── usr
└── var

无论底层有多少块磁盘、多少个分区、多少种文件系统,用户和进程看到的都是这棵统一的路径树。

这和 Windows 里常见的 C:\D:\ 多盘符模型不同。Linux 的模型是:

先有一棵以 / 为根的目录树
再把不同文件系统挂载到这棵树的不同位置

例如:

/ 来自 /dev/nvme0n1p2
/boot 来自 /dev/nvme0n1p1
/home 来自 /dev/nvme0n1p3
/proc 来自 proc 虚拟文件系统
/sys 来自 sysfs 虚拟文件系统
/run 来自 tmpfs

其中最关键的是 /。因为其他挂载点都要挂到这棵树的某个位置,而这棵树本身必须先存在。

所以 Linux 启动时必须先解决一个根本问题:

系统启动后,哪个文件系统来承担 / 这个根?

承担这个角色的文件系统,就叫 rootfs

rootfs 是一种角色,不是一种格式

理解 rootfs 最容易出错的地方,是把它和磁盘、分区、文件系统格式混在一起。

可以按层次拆开:

磁盘设备: /dev/nvme0n1、/dev/sda、/dev/mmcblk0
分区: /dev/nvme0n1p2、/dev/sda1
文件系统格式: ext4、xfs、btrfs、squashfs、erofs
挂载点: /、/boot、/home、/var
系统角色: rootfs,也就是挂载到 / 的那个文件系统

举一个普通 Linux 主机的例子:

/dev/nvme0n1p2 是一个分区
这个分区里创建了 ext4 文件系统
这个 ext4 文件系统被挂载到 /
于是它在这个系统里承担 rootfs 角色

所以:

  • rootfs 不是磁盘。
  • rootfs 不是分区。
  • rootfs 不是 GPT、MBR 这样的分区表格式。
  • rootfs 也不是 ext4xfsbtrfs 这样的文件系统格式。
  • rootfs 是“哪个文件系统正在作为 / 使用”的角色名。

同样是 rootfs,底层可以完全不同:

ext4 挂载到 / -> ext4 文件系统承担 rootfs 角色
btrfs 挂载到 / -> btrfs 文件系统承担 rootfs 角色
squashfs 挂载到 / -> squashfs 镜像承担 rootfs 角色
tmpfs 挂载到 / -> 内存文件系统承担 rootfs 角色
NFS 挂载到 / -> 网络文件系统承担 rootfs 角色
overlayfs 挂载到 / -> overlayfs 叠加结果承担 rootfs 角色

这就是为什么 rootfs 这个词在嵌入式、Live CD、容器、Docker、initramfs 里都能出现。它不是描述底层材料,而是描述这个文件系统在当前系统中的职责。

为什么要单独给它一个名字?

因为 / 不是普通挂载点。

普通挂载点可以晚一点出现。比如 /home 不挂载,系统可能还能进入维护模式;/mnt/data 不挂载,最多是某些数据不可用。

/ 不存在,系统就没有路径命名空间的起点。内核后续要启动第一个用户态进程,要找 /sbin/init/init 或 systemd;用户态工具要访问 /etc/dev/usr;这些都依赖根目录树已经存在。

因此 rootfs 是系统启动链路中的核心角色:

内核启动
-> 准备早期根文件系统
-> 找到或构造真正的 rootfs
-> 切换到这个 rootfs
-> 启动 init / systemd
-> 系统进入正常运行

给它一个单独名字,可以跨越很多不同场景讨论同一件事:

  • 嵌入式里说“烧录 rootfs”,通常指烧录根文件系统镜像或目录树。
  • Docker 里说“容器 rootfs”,指容器进程看到的 /
  • initramfs 里说“早期 rootfs”,指内核启动初期解包出来的临时根。
  • LXC 里说“rootfs tarball”,指能作为容器根目录的完整目录树。

如果不用 rootfs 这个词,每次都要说“那个会被挂载为 /、让系统或容器进程作为根目录看到的文件系统”,太长,也不利于讨论启动流程和容器隔离。

rootfs 里面通常有什么?

一个能作为 Linux 根文件系统使用的目录树,通常至少要包含用户态运行所需的基本目录和文件。

常见结构类似:

/
├── bin
├── dev
├── etc
├── lib
├── lib64
├── proc
├── root
├── run
├── sbin
├── sys
├── tmp
├── usr
└── var

不同发行版、不同用途会有差异。桌面/服务器发行版的 rootfs 通常很大,包含包管理器、系统服务、shell、库文件、配置文件等。嵌入式系统的 rootfs 可能非常小,只包含 BusyBox 和少量配置。容器镜像的 rootfs 则可能只包含运行某个应用所需的最小依赖。

从这个角度看,rootfs 也经常被用来指“一个可作为根目录使用的目录树内容”。

例如:

rootfs.tar.gz 一个打包好的根文件系统目录树
rootfs.ext4 一个 ext4 格式的根文件系统镜像
rootfs.squashfs 一个 squashfs 格式的只读根文件系统镜像

这里的 rootfs 不是说它们的格式都叫 rootfs,而是说它们的用途是“作为根文件系统”。

Linux 启动时 rootfs 怎么来?

一个简化的 Linux 启动过程可以这样理解:

1. Bootloader 加载内核和启动参数
2. 内核初始化 CPU、内存、驱动等基础设施
3. 内核准备早期根文件系统
4. 内核根据 root= 参数或 initramfs 逻辑找到真正的根
5. 挂载真正的 rootfs 到 /
6. 启动 /sbin/init、/init 或 systemd

启动参数里常见的 root= 就是在告诉内核真正的根文件系统来源。

比如:

root=UUID=xxxx-xxxx
root=/dev/nvme0n1p2
root=/dev/mmcblk0p2
root=/dev/nfs

这不是指定“rootfs 格式”,而是指定“哪个设备或来源要被挂载成 /”。

很多现代发行版还会使用 initramfs。它是一个早期用户态环境,通常被内核解包到内存中的早期根文件系统里。它的任务包括加载磁盘驱动、解密磁盘、组装 RAID/LVM、寻找真正的根文件系统,然后切换过去。

所以启动早期可能存在两个阶段:

早期 rootfs:initramfs 提供的临时根
真正 rootfs:磁盘、网络或其他来源上的最终根

这也是为什么在启动相关文档里,rootfs 这个词有时会显得更加“内核化”:Linux 内核本身确实有早期 rootfs 的概念。

临时 rootfs 如何切换到真正 rootfs?

从启动过程看,临时 rootfs 切换到真正 rootfs 的过程通常发生在 initramfs 阶段。

这个过程常被描述为:

从 initramfs 切换到 real rootfs

在用户态工具里,常见命令名是 switch_root。底层可能涉及 mount、移动挂载点、chrootpivot_root 等机制,但现代 initramfs 里经常由 switch_root 或发行版自己的 initramfs 脚本封装。

一个更完整的启动链路可以这样看:

Bootloader
-> 加载 Linux kernel
-> 加载 initramfs
-> kernel 启动
-> kernel 解包 initramfs 到早期 rootfs
-> 执行 initramfs 里的 /init
-> /init 找到真正 rootfs
-> 挂载真正 rootfs
-> 切换 / 到真正 rootfs
-> 执行真正 rootfs 里的 /sbin/init 或 systemd

关键点是:切换 rootfs 的工作通常不是内核全自动完成的,而是 initramfs 里的早期用户态脚本完成的。

启动早期,当前 / 还是 initramfs 提供的临时 rootfs:

当前 /
= initramfs 提供的临时 rootfs

initramfs 里的 /init 通常会做这些事:

# 1. 挂载早期需要的虚拟文件系统
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev

# 2. 加载必要驱动、等待设备出现
# 例如磁盘、USB、NVMe、RAID、LVM、加密卷、网络等

# 3. 找到真正的 root 设备
# 可能来自 /proc/cmdline 里的 root=UUID=...
# 也可能来自脚本、网络、标签、LVM 名称等

# 4. 把真正 rootfs 挂到临时目录
mount /dev/nvme0n1p2 /newroot

# 5. 切换到真正 rootfs
exec switch_root /newroot /sbin/init

切换前,文件系统视图大概是:

/ initramfs 临时 rootfs
├── init 早期启动脚本
├── bin
├── dev
├── proc
├── sys
└── newroot 真正 rootfs 临时挂载点
├── sbin/init
├── etc
├── usr
└── var

执行:

exec switch_root /newroot /sbin/init

之后,文件系统视图变成:

/ 真正 rootfs
├── sbin/init 或 systemd
├── etc
├── usr
├── var
├── dev
├── proc
└── sys

原来的 initramfs 临时 rootfs 会被释放,或者至少从正常系统视图里消失。随后系统继续从真正 rootfs 里的 initsystemd 启动。

为什么不让内核直接挂载真正 rootfs?因为真正 rootfs 很多时候不是一开始就能直接访问的。

它可能位于:

  • 加密磁盘里,需要先解锁;
  • LVM 或 RAID 上,需要先组装卷;
  • NVMe、USB、SCSI 设备上,需要先加载驱动;
  • 网络 NFS 上,需要先配置网络;
  • btrfs 子卷、只读 squashfs、overlay 组合上,需要先执行额外挂载逻辑。

所以 initramfs 的角色是:

提供一个最小临时 Linux 环境,用来找到、准备、挂载真正的 rootfs,然后把系统控制权交给它。

这里还有一个容易误解的地方:临时 rootfs 并不是被“转换成”正式 rootfs。真实发生的是:

1. 临时 rootfs 先作为 /
2. 真正 rootfs 被挂载到 /newroot
3. 启动脚本把进程视角里的 / 切换到 /newroot
4. 新的 /sbin/init 接管系统启动
5. 临时 rootfs 退出历史舞台

也就是说,这是一次“根目录视角切换”,不是文件系统格式转换。

在 Linux 上怎么查看当前 rootfs?

最推荐的命令是:

findmnt /

它会显示当前 / 的来源、文件系统类型和挂载选项。

也可以只看关键字段:

findmnt -no SOURCE,FSTYPE,OPTIONS /

可能输出类似:

/dev/nvme0n1p2 ext4 rw,relatime

这说明当前作为 / 的文件系统来自 /dev/nvme0n1p2,格式是 ext4

在 Docker 容器里可能看到:

overlay overlay rw,relatime,lowerdir=...,upperdir=...,workdir=...

这说明容器里的 / 来自一个 overlayfs 叠加结果。

还可以看内核提供的挂载信息:

cat /proc/mounts | awk '$2 == "/" {print}'

或者:

awk '$5 == "/" {print}' /proc/self/mountinfo

查看启动参数:

cat /proc/cmdline

如果里面有 root=UUID=...root=/dev/...,那就是内核启动时被告知的根文件系统来源。

需要注意的是,正常系统启动完成后,findmnt /mount 里不一定会直接出现 rootfs 这个关键词。你更常看到的是具体来源和具体格式,比如:

/dev/nvme0n1p2 on / type ext4

或者:

overlay on / type overlay

能不能看到 rootfs 这个词,取决于当前环境和启动阶段。早期启动环境、initramfs、某些容器或特殊系统里可能能看到:

rootfs / rootfs rw 0 0

但看不到 rootfs 这个词,并不代表没有 rootfs。只要系统里存在 /,就一定有某个文件系统承担了 rootfs 角色。

rootfs 和 /boot、/home 有什么区别?

/boot/home/var 这些目录可以是 rootfs 内部的普通目录,也可以是额外挂载进来的文件系统。

比如一个简单系统可能只有一个分区:

/dev/nvme0n1p2 -> ext4 -> 挂载到 /

这时 /boot/home/var 都只是 rootfs 里的普通目录。

另一个系统可能拆成多个分区:

/dev/nvme0n1p2 -> ext4 -> /
/dev/nvme0n1p1 -> vfat -> /boot/efi
/dev/nvme0n1p3 -> xfs -> /home

这时 /home 是另一个文件系统挂载过来的。但从路径树上看,它仍然位于 / 下面。

所以 rootfs 的特殊性在于:它是整棵树的根。其他文件系统都要挂到这棵树的某个节点上。

Docker 容器里的 rootfs 是什么?

Docker 容器也有自己的 /

这并不是说 Docker 启动了一个新内核。普通 Docker 容器和宿主机共享同一个 Linux 内核,但它通过 namespace 让容器进程看到一套隔离后的文件系统视图。

Docker 镜像本质上可以理解为一组文件系统层:

只读镜像层 1
只读镜像层 2
只读镜像层 3
容器可写层

启动容器时,Docker 通常通过 overlay2 把这些层叠加成一个最终目录树。容器进程看到的 /,就是这个叠加结果。

简化表示:

image readonly layers
+
container writable layer
=
container rootfs

所以当你运行:

docker run -it ubuntu:24.04 /bin/bash

容器里的 / 来自 ubuntu:24.04 镜像层加上这个容器自己的可写层。

而:

docker run -it alpine:3.20 /bin/sh

容器里的 / 来自 Alpine 镜像层加上可写层。

这两个容器看到的 rootfs 内容不同,因为镜像不同。

Docker 启动时能不能更改容器 rootfs?

可以,但要分层理解。

普通 Docker 使用方式里,你通常不是在 docker run 时直接指定一个任意目录作为 /,而是通过镜像决定 rootfs 的来源:

docker run ubuntu:24.04
docker run alpine:3.20
docker run debian:bookworm

如果你想让容器使用自己的 rootfs,常见做法是构建或导入一个镜像。

例如,把一个已有 rootfs 目录打包成镜像:

tar -C /path/to/rootfs -cf rootfs.tar .
docker import rootfs.tar my-rootfs:latest
docker run -it my-rootfs:latest /bin/sh

这时容器内的 / 就来自你导入的那个目录树。

也可以用 Dockerfile 构建:

FROM debian:bookworm
RUN apt-get update && apt-get install -y curl
COPY ./app /app
CMD ["/app/start.sh"]

构建出的镜像会形成新的 rootfs 内容。

但普通 docker run 没有一个类似这样的参数:

docker run --rootfs /some/host/path

Docker 把底层 rootfs 管理封装在镜像、存储驱动和 OCI runtime 后面。

更底层的 OCI runtime,比如 runc,确实有 rootfs 路径的概念。Docker 最终会把容器配置转换成 OCI 规范,再交给底层 runtime 执行。底层 runtime 会设置 mount namespace,并让容器进程进入指定的 rootfs。

可以这样理解:

Docker CLI
-> 通过 image 选择 rootfs 内容
-> containerd 准备容器运行配置
-> OCI spec 描述 rootfs path
-> runc 设置 namespace、mount、pivot_root/chroot
-> 容器进程看到自己的 /

所以答案是:

Docker 容器启动时当然会设置容器内的 rootfs,但普通用户通常通过“换镜像、构建镜像、导入 rootfs tarball”来改变它,而不是直接在 docker run 中指定一个宿主机目录作为 /

bind mount 会改变 rootfs 吗?

不会。bind mount 或 volume 只是把某个目录挂进容器内的某个路径。

例如:

docker run -v /host/data:/data ubuntu

这会让容器内 /data 对应宿主机 /host/data,但容器的 / 仍然来自镜像和可写层组成的 rootfs。

你也可以挂载到更关键的位置:

docker run -v /host/etc:/etc ubuntu

这会覆盖容器内 /etc,但它仍然不是替换整个 rootfs,只是覆盖 rootfs 里面的一个子路径。这样做很容易破坏容器内部依赖,除非你非常清楚后果。

所以:

换镜像 / 导入 rootfs -> 改变容器 rootfs 的基础内容
bind mount / volume -> 在 rootfs 上额外挂载或覆盖局部路径

rootfs、chroot、pivot_root 的关系

rootfs 是“作为 / 的文件系统或目录树”。

chrootpivot_root 是让进程改变它看到的根目录的机制。

chroot 会把某个目录变成进程视角下的 /。例如:

sudo chroot /path/to/rootfs /bin/sh

这个 shell 会把 /path/to/rootfs 当作自己的 /

chroot 不是完整容器隔离。它主要改变路径解析根目录,不自动隔离进程、网络、用户、mount namespace 等。

容器 runtime 通常会结合 mount namespace、pivot_root、bind mount、cgroup、user namespace 等机制,构造出更完整的隔离环境。

可以粗略理解:

rootfs 目标目录树或文件系统
chroot 改变进程看到的 /
pivot_root 在 mount namespace 中切换根挂载
namespace 隔离进程看到的系统视图
cgroup 限制和统计资源

常见误解

误解一:rootfs 是一种文件系统格式

不是。

ext4xfsbtrfssquashfs 是文件系统格式。rootfs 是作为 / 的角色。

一个 ext4 文件系统可以是 rootfs,也可以挂到 /home,也可以挂到 /data。它是不是 rootfs,取决于它挂载在哪里、在当前系统里承担什么职责。

误解二:rootfs 就是根分区

不完全对。

在很多传统 Linux 安装中,rootfs 确实来自某个“根分区”,比如 /dev/sda2。但 rootfs 不一定来自分区。

它也可以来自:

  • 内存中的 initramfs
  • 网络上的 NFS
  • 只读 squashfs 镜像
  • overlayfs 叠加结果
  • 容器运行时准备的目录树
  • LVM、RAID、加密块设备之上的文件系统

所以“根分区”只是 rootfs 的一种常见承载方式,不等于 rootfs 本身。

误解三:容器有自己的 rootfs,所以容器有自己的内核

不是。

容器有自己的文件系统视图,也就是自己的 /,但普通容器和宿主机共享内核。

容器隔离主要依赖 Linux 内核提供的 namespace、cgroup、capability、seccomp、mount 等机制。rootfs 只是其中的文件系统视图部分。

误解四:看不到 rootfs 关键词,就说明系统没有 rootfs

不是。

启动完成后的系统往往显示的是具体来源和具体类型:

/dev/nvme0n1p2 on / type ext4

这行的意思就是:/dev/nvme0n1p2 上的 ext4 文件系统正在承担 rootfs 角色。

rootfs 是角色名,不一定会原样出现在命令输出里。

一张总览图

可以把这些概念放到同一张图里:

物理磁盘 / 虚拟磁盘 / 镜像文件 / 网络存储 / 内存
|
v
分区 / 块设备 / 目录树 / 镜像层
|
v
文件系统格式或叠加机制
ext4 / xfs / btrfs / squashfs / tmpfs / overlayfs / NFS
|
v
挂载到某个路径
/、/boot、/home、/data
|
v
如果挂载到 /,它在当前命名空间里就是 rootfs

再看 Docker:

Docker image layers
|
v
overlay2 合成 merged 目录
|
v
OCI runtime 把它设置为容器进程的 /
|
v
容器内看到的 rootfs

最后总结

rootfs 最核心的含义是:

在某个 Linux 系统或容器的文件系统命名空间里,作为根目录 / 使用的那个文件系统或目录树。

它不是磁盘,不是分区,也不是文件系统格式。

它更像一个系统角色:

  • 从磁盘层看,它可能来自某个分区。
  • 从文件系统格式看,它可能是 ext4、btrfs、squashfs、overlayfs 等。
  • 从挂载层看,它挂载在 /
  • 从系统启动和运行视角看,它承担 rootfs 角色。

理解 rootfs 的关键,是把“底层存储是什么”和“在 Linux 命名空间里扮演什么角色”分开。

一句话记忆:

rootfs 不是“某种格式”,而是“当前这个 Linux 环境里的 / 从哪里来”。

DuckDuckGo 的搜索结果从哪里来?

· 阅读需 9 分钟

DuckDuckGo 最常见的标签是“隐私搜索引擎”。它的核心卖点不是搜索结果一定比 Google 或 Bing 更全,而是默认不追踪用户、不建立个人搜索画像,也不会把用户的搜索历史交给广告系统做长期画像。

但很多人真正想问的是另一个问题:DuckDuckGo 的搜索结果到底是自己爬来的,还是聚合了别的搜索引擎?

简短答案是:DuckDuckGo 不是一个完全自建网页索引的搜索引擎,也不是简单套壳某一个搜索引擎。它更像是一个多来源搜索系统:一部分结果来自自有爬虫和自有索引,一部分来自垂直数据源,一部分传统网页链接和图片结果则很大程度来自 Bing。

这个结构决定了 DuckDuckGo 的产品气质:它优先把“隐私、聚合、答案体验”做好,而不是把自己包装成一个完全独立于所有大搜索索引的系统。

DuckDuckGo 是什么?

DuckDuckGo 是一个搜索引擎和隐私产品公司。用户最熟悉的是 duckduckgo.com 搜索框,但它现在也提供浏览器、浏览器扩展、邮件保护、App Tracking Protection、Duck.ai 等产品。

如果只看搜索,它和 Google、Bing、Brave Search 一样,给用户返回网页、图片、新闻、本地信息、即时答案等结果。但 DuckDuckGo 的差异主要有两点:

  • 隐私默认开启:它不把搜索行为和个人身份绑定起来。
  • 结果来源是组合式的:它把多个来源合成一个搜索结果页,而不是只依赖单一自有网页索引。

所以,DuckDuckGo 的重点不是“我拥有整个互联网的最大索引”,而是“我用尽量匿名的方式,从多个来源合成一个可用的搜索体验”。

搜索结果来源:不是单一索引

DuckDuckGo 官方帮助文档对结果来源的描述很直接:很多搜索类别中,专门的数据源通常比通用搜索引擎更适合回答问题。例如餐厅、歌词、体育比分等主题,可能会来自 Tripadvisor、Musixmatch、Sportradar、Wikipedia 等专门来源或众包来源。

这类结果通常会以 Instant Answers 的形式出现,也就是搜索结果页顶部或中间的直接答案、摘要、卡片、结构化信息等。

同时,DuckDuckGo 也维护自己的爬虫 DuckDuckBot 和多个索引,用来支持搜索结果。但对于更传统的网页链接和图片结果,DuckDuckGo 官方说明是“很大程度来自 Bing”。

因此可以把 DuckDuckGo 的搜索结果拆成三层:

层级来源作用
即时答案和结构化信息专门数据源、众包站点、自有索引回答事实型、垂直型、结构化问题
自有爬虫和自有索引DuckDuckBot、DuckDuckGo 内部索引改进结果、支撑特定功能和部分搜索体验
传统网页链接和图片很大程度来自 Bing提供通用网页搜索的主体覆盖

这就是为什么说 DuckDuckGo 是混合型搜索引擎,而不是纯自建索引搜索引擎。

DuckDuckBot 是什么?

DuckDuckBot 是 DuckDuckGo 的网页爬虫。它会访问网页、读取公开页面,并把这些信息用于改进 DuckDuckGo 的搜索结果。

站长可以在服务器日志中看到 DuckDuckBot 的 User-Agent。DuckDuckGo 也公开了 DuckDuckBot 的说明和 IP 信息,方便站点识别和管理。

不过,存在 DuckDuckBot 并不等于 DuckDuckGo 的主搜索完全依赖自有索引。一个搜索引擎可以有自己的爬虫,用于补充结果、构建垂直索引、做质量判断、支持 AI 检索或其他功能,但它仍然可能在通用网页搜索上依赖外部大索引。

DuckDuckGo 当前就是这种状态:它有自己的爬虫和索引能力,但传统网页搜索仍明显依赖 Bing。

自有索引有多大?

这是最容易被误解的地方。

截至 2026-06-18,没有看到 DuckDuckGo 官方公开披露一个可靠的“自有网页索引总量”。它公开说自己维护 DuckDuckBot 和多个索引,也公开谈到正在建设 full web search index,但没有给出类似“多少亿网页”或“多少万亿网页”的正式规模数字。

所以比较稳妥的表述是:

  • DuckDuckGo 有自有爬虫。
  • DuckDuckGo 有多个自有索引。
  • DuckDuckGo 正在建设更完整的自有网页搜索索引。
  • 但 DuckDuckGo 没有公开披露其自有网页索引的总体规模。

因此,如果有人声称 DuckDuckGo 有某个具体数量的网页索引,需要特别看来源。如果不是官方披露,最好只当作估算或传闻。

DuckDuckGo 为什么要建设自己的 full web index?

DuckDuckGo 过去并不是没有索引能力。它长期拥有各种垂直索引和爬取能力,例如 Instant Answers、歌词、特定主题索引等。但它直到近几年才更明确地建设 full web search index。

背后的一个重要原因是 AI 搜索和 AI 问答。

DuckDuckGo 现在有 Search Assist 和 Duck.ai。Search Assist 是搜索结果页上的 AI 辅助回答,Duck.ai 是聊天式 AI 产品。这两类产品都需要高质量、及时、可检索的网页数据做 grounding,也就是用真实网页内容支撑回答,降低幻觉。

如果 AI 产品每次都要依赖第三方搜索数据,DuckDuckGo 在成本、延迟、可控性、隐私、结果质量反馈上都会受限制。建设自有索引,可以让它在内部形成更紧密的反馈循环:搜索产品本身就是索引的客户,用户搜索行为又能反过来帮助改进相关性判断。

不过,这不等于 DuckDuckGo 已经完全切换到自有索引。更准确的理解是:DuckDuckGo 正在把自有索引从“补充能力”推进为“更核心的基础设施”,尤其服务于 Search Assist、Duck.ai 和未来的 AI 搜索体验。

覆盖哪些领域?

DuckDuckGo 没有按行业公开披露自有索引覆盖率,所以不能说它在某个行业覆盖多少百分比。

从官方说明和产品形态看,它覆盖的是几类不同需求:

  • 通用网页搜索:传统网页链接、图片、新闻等,其中网页链接和图片很大程度依赖 Bing。
  • 垂直答案:餐厅、歌词、体育比分、百科知识等,会使用专门数据源、众包数据和自有索引。
  • 本地和地图相关信息:会结合地图、本地数据和隐私保护机制。
  • AI 辅助回答:Search Assist 和 Duck.ai 会越来越依赖自有网页索引来做检索增强和答案 grounding。

换句话说,DuckDuckGo 的覆盖不是一个单一“全网索引库”问题,而是多个数据层共同组成的搜索体验。

DuckDuckGo 和 Brave Search 的区别

Brave Search 是一个很好的对照对象,因为它也主打隐私,但它更强调“独立网页索引”。

Brave 在 2023 年宣布移除搜索结果页中最后残留的 Bing 依赖,称 Brave Search 已达到 100% 独立。到 2026 年,Brave 官方博客称 Brave Search API 基于约 400 亿网页的独立索引,并且每天新增或刷新超过 1 亿网页。

这和 DuckDuckGo 的定位不同。

维度DuckDuckGoBrave Search
核心定位隐私搜索 + 多来源结果聚合隐私搜索 + 独立网页索引
传统网页结果很大程度来自 Bing,同时有自有爬虫和索引补充官方称网页搜索来自自有独立索引
自有索引规模未公开总体规模官方称约 400 亿网页
AI 搜索动机Search Assist、Duck.ai 推动自有 full web index 建设自有索引已经是产品和 API 的核心卖点
结果来源策略聚合专门来源、众包来源、自有索引、Bing自有爬虫、自有索引、Web Discovery Project 等
更适合谁想要成熟隐私搜索体验、接受 Bing 结果底座的用户更关心搜索索引独立性、想避开 Google/Bing 生态的用户

一句话概括:DuckDuckGo 更像是“隐私优先的多源搜索引擎”,Brave Search 更像是“隐私优先的独立索引搜索引擎”。

如何理解 DuckDuckGo 的价值?

如果只用“有没有完全自建索引”来评价 DuckDuckGo,会漏掉它真正的产品价值。

DuckDuckGo 的价值主要在于:

  • 它降低了普通用户使用隐私搜索的门槛。
  • 它用代理和匿名化机制减少合作伙伴看到用户身份的机会。
  • 它把多个来源合成一个足够可用的搜索结果页。
  • 它不把用户搜索历史变成广告画像。
  • 它正在为 AI 搜索建设更多自有数据基础设施。

它的局限也很清楚:

  • 通用网页搜索的独立性不如 Brave Search。
  • 自有索引规模不透明。
  • 传统网页结果在覆盖和排序上会受到 Bing 生态影响。
  • 如果用户追求完全独立于 Bing/Google 的搜索索引,DuckDuckGo 不是最强答案。

所以,DuckDuckGo 不是“伪搜索引擎”,也不是“完全独立搜索引擎”。它是一个隐私优先、结果来源混合、正在增强自有索引能力的搜索产品。

总结

DuckDuckGo 的搜索结果不是单纯自己收集,也不是简单聚合其他搜索引擎结果。它的真实形态是混合架构:专门数据源和众包数据负责很多即时答案,自有 DuckDuckBot 和多个索引用来补充和改进搜索体验,传统网页链接和图片则很大程度来自 Bing。

截至 2026-06-18,DuckDuckGo 没有公开可靠的自有网页索引总量。它正在建设 full web search index,尤其服务于 Search Assist 和 Duck.ai,但这仍是一个逐步推进的过程。

和 Brave Search 相比,DuckDuckGo 的优势在隐私产品成熟度和多源聚合体验;Brave Search 的优势在独立索引和对 Bing/Google 依赖更低。如果问题是“谁更独立”,答案更偏 Brave Search;如果问题是“哪个隐私搜索引擎更成熟、更日常可用”,DuckDuckGo 仍然是重要选项。

参考资料

Alibaba ROCK:面向 Agentic RL 的环境与沙箱框架

· 阅读需 8 分钟

Alibaba 的 ROCK,全名是 Reinforcement Open Construction Kit。它不是一个新的强化学习算法库,也不是一个通用的容器平台,而是一个面向强化学习环境的开发和管理框架。

更具体地说,ROCK 关注的是一个在 Agentic RL 里越来越重要的问题:当智能体需要进入真实或近似真实的交互环境中执行任务时,环境应该如何创建、隔离、调度、复用和销毁?

一句话概括:

ROCK 是给强化学习智能体使用的环境与沙箱基础设施。

它的价值不在于替你训练模型,而在于替训练系统提供大量可控、可复现、可规模化的交互环境。

OpenClaw插件开发指南

· 阅读需 2 分钟

准备开发环境

安装pnpm

参考 pnpm Installation :

npm install -g pnpm@latest-10

准备OpenClaw

根据 OpenClaw README 介绍:

1)克隆代码

git clone https://github.com/openclaw/openclaw.git
cd openclaw

2)构建

pnpm install
pnpm ui:build # auto-installs UI deps on first run
pnpm build

3)配置

pnpm openclaw onboard

或者直接修改$HOME/.openclaw/openclaw.json文件。

启动OpenClaw

pnpm gateway:watch

在此模式下,配置文件的变动会直接触发Gateway的配置重加载。

配置插件

1)克隆插件

https://github.com/Timandes/fnos-openclaw.git
cd fnos-openclaw

2)构建

npm install
npm run build

3)修改配置

修改$HOME/.openclaw/openclaw.json文件,在plugins.load.paths中增加插件的路径:

plugins:
load:
paths:
- /path/to/fnos-openclaw

然后,在plugins.entires中启用插件并增加对应的插件配置:

plugins:
entries:
fnos:
enabled: true
config:
defaultAccount: "main"
accounts:
main:
endpoint: "nas.example.com:5666"
authType: "password"
username: "admin"
password: "your-password"
backup:
endpoint: "backup.example.com:5666"
authType: "token"
token: "your-token"
longToken: "your-long-token"
secret: "your-secret"

4)关闭并重启Gateway

重新进入OpenClaw的源代码目录,关闭前面的gateway:watch进程,重新启动:

pnpm gateway:watch

MCP注册表(2025-09-08)—— 发布者CLI命令参考

· 阅读需 3 分钟

翻译自:https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/cli/commands.md

mcp-publisher CLI 工具的完整命令参考。

请参阅发布指南,了解使用CLI发布服务器的详细步骤。

安装

通过 Homebrew 安装(macOS/Linux):

$ brew install mcp-publisher

全局选项

所有命令支持:

  • --help, -h - 显示命令帮助
  • --registry - 注册表URL(默认值:https://registry.modelcontextprotocol.io

命令

mcp-publisher init

生成一个带有自动检测功能的server.json模板。

用法:

mcp-publisher init [options]

行为:

  • 在当前目录中创建 server.json
  • 自动检测包管理器(如 package.jsonsetup.py 等)
  • 在可能的情况下预填充字段
  • 提示输入缺失的必填字段

示例输出:

{
"name": "io.github.username/server-name",
"description": "TODO: Add server description",
"version": "1.0.0",
"packages": [
{
"registry_type": "npm",
"identifier": "detected-package-name",
"version": "1.0.0"
}
]
}

mcp-publisher login <method>

使用注册表进行认证。

认证方法:

GitHub互动

mcp-publisher login github [--registry=URL]
  • 打开浏览器进行GitHub OAuth流程
  • 授予对 io.github.{username}/*io.github.{org}/* 命名空间的访问权限。

GitHub OIDC(持续集成/持续交付)

mcp-publisher login github-oidc [--registry=URL]
  • 自动使用 GitHub Actions OIDC 令牌
  • 在工作流中需要具有 id-token: write 权限。
  • 不需要浏览器交互。

另请参阅[从 GitHub Actions 发布的指南]。

DNS 验证

mcp-publisher login dns --domain=example.com --private-key=HEX_KEY [--registry=URL]
  • 验证域名所有权通过DNS TXT记录
  • 授予对 com.example.* 命名空间的访问权限
  • 需要 Ed25519 私钥(64 个字符的十六进制)。

设置:

# 生成密钥对
openssl genpkey -algorithm Ed25519 -out key.pem

# 获取用于DNS记录的公钥
openssl pkey -in key.pem -pubout -outform DER | tail -c 32 | base64

# 添加DNS TXT记录:
# example.com. IN TXT "v=MCPv1; k=ed25519; p=PUBLIC_KEY"

# 提取用于登录的私钥
openssl pkey -in key.pem -noout -text | grep -A3 "priv:" | tail -n +2 | tr -d ' :\n'

HTTP验证

mcp-publisher login http --domain=example.com --private-key=HEX_KEY [--registry=URL]
  • 通过HTTPS端点验证域所有权
  • 授予对 com.example.* 命名空间的访问权限
  • 需要 Ed25519 私钥(64 个字符的十六进制)。

设置:

# 生成密钥对(与DNS相同)
openssl genpkey -algorithm Ed25519 -out key.pem

# 托管公钥到以下地址:
# https://example.com/.well-known/mcp-registry-auth
# 内容:v=MCPv1; k=ed25519; p=PUBLIC_KEY

匿名(测试)

mcp-publisher login none [--registry=URL]
  • 没有身份验证 - 仅供本地测试
  • 仅适用于本地注册表实例

mcp-publisher publish

将服务器发布到注册表。

有关发布流程的详细指南,请参阅发布指南

用法:

mcp-publisher publish [options]

选项:

  • --file=PATH - 指向 server.json 的路径 (默认: ./server.json)
  • --registry=URL - 覆盖注册表 URL
  • --dry-run - 验证但不发布

流程:

  1. 根据模式验证 server.json
  2. 验证包所有权(参见[官方注册要求])
  3. 检查命名空间认证
  4. 发布到注册库

示例:

# 基本发布
mcp-publisher publish

# 干运行验证
mcp-publisher publish --dry-run

# 自定义文件位置
mcp-publisher publish --file=./config/server.json

mcp-publisher logout

清除存储的身份验证凭据。

用法:

mcp-publisher 注销

行为:

  • 删除 ~/.mcp_publisher_token
  • 不会在服务器端撤销令牌

配置

令牌存储

认证令牌以JSON格式存储在~/.mcp_publisher_token中。

{
"token": "jwt-token-here",
"registry_url": "https://registry.modelcontextprotocol.io",
"expires_at": "2024-12-31T23:59:59Z"
}

MCP注册表(2025-09-08)—— 通用注册表 API 规范

· 阅读需 2 分钟

翻译自:https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/api/generic-registry-api.md

一个标准化的RESTful HTTP API,用于MCP注册表提供一致的端点,以发现和检索MCP服务器。

另请参阅:

浏览完整的API规范

📋 交互式查看完整的 API 规范:在 OpenAPI 查看器中打开 openapi.yaml,例如 Stoplight Elements

官方注册表在此基础上还有一些额外的端点和限制。有关详细信息,请参阅官方注册表 API 规范

快速参考

核心端点

  • GET /v0/servers - 列出所有带有分页功能的服务器
  • GET /v0/servers/{id} - 根据UUID获取服务器详细信息
  • POST /v0/publish - 发布新服务器(可选,注册表特定的身份验证)

认证

  • 读取操作:无需身份验证
  • 写操作:注册表特定的身份验证(如果支持)。

内容类型

所有请求和响应都使用 application/json

基本示例:列出服务器

curl https://registry.example.com/v0/servers?limit=10
{
"servers": [
{
"name": "io.modelcontextprotocol/filesystem",
"description": "Filesystem operations server",
"status": "active",
"version": "1.0.2"
}
],
"metadata": {
"count": 10,
"next_cursor": "eyJ..."
}
}

要查看完整的端点文档,请在架构查看器中查看 OpenAPI 规范。

MCP注册表(2025-09-08)—— server.json 格式规范

· 阅读需 9 分钟

翻译自:https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/generic-server-json.md

server.json 文件是一种标准化的方式,用于描述 MCP 服务器,以便进行注册表发布、客户端发现和包管理。

另请参阅:

  • 有关创建和使用 server.json 文件的逐步说明,请参阅发布指南
  • 有关发布到官方注册表时验证要求的理解,请参阅官方注册表要求

浏览完整架构

📋 以交互方式查看完整规范: 在架构查看器中打开 server.schema.json,例如 json-schema.app

该架构包含所有字段定义、验证规则、示例和详细描述。

官方注册表在此基础上还有一些额外的限制。详情请参见官方注册表要求

例子

基本服务器与NPM包

{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
"name": "io.modelcontextprotocol.anonymous/brave-search",
"description": "MCP server for Brave Search API integration",
"status": "active",
"website_url": "https://anonymous.modelcontextprotocol.io/examples",
"repository": {
"url": "https://github.com/modelcontextprotocol/servers",
"source": "github"
},
"version": "1.0.2",
"packages": [
{
"registry_type": "npm",
"registry_base_url": "https://registry.npmjs.org",
"identifier": "@modelcontextprotocol/server-brave-search",
"version": "1.0.2",
"transport": {
"type": "stdio"
},
"environment_variables": [
{
"name": "BRAVE_API_KEY",
"description": "Brave Search API Key",
"is_required": true,
"is_secret": true
}
]
}
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"tool": "npm-publisher",
"version": "1.0.1",
"build_info": {
"timestamp": "2023-12-01T10:30:00Z"
}
}
}
}

单体仓库中的带子文件夹的服务器

对于位于大型代码库(单仓库结构)子目录中的MCP服务器,请使用 subfolder 字段来指定相对路径:

{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
"name": "io.modelcontextprotocol/everything",
"description": "MCP服务器,测试MCP协议的所有功能",
"status": "active",
"repository": {
"url": "https://github.com/modelcontextprotocol/servers",
"source": "github",
"subfolder": "src/everything"
},
"version": "0.6.2",
"packages": [
{
"registry_type": "npm",
"registry_base_url": "https://registry.npmjs.org",
"identifier": "@modelcontextprotocol/everything",
"version": "0.6.2",
"transport": {
"type": "stdio"
}
}
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"tool": "npm-publisher",
"version": "1.0.1",
"build_info": {
"timestamp": "2023-12-01T10:30:00Z"
}
}
}
}

启动MCP服务器所需的常量(固定)参数

假设你的MCP服务器应用程序需要mcp start命令行参数才能以MCP服务器模式启动。将其表示为类似这样的位置参数:

{
"name": "io.github.joelverhagen/knapcode-samplemcpserver",
"description": "随机数和随机天气的示例 NuGet MCP 服务器",
"version": "0.4.0-beta",
"packages": [
{
"registry_type": "nuget",
"registry_base_url": "https://api.nuget.org",
"identifier": "Knapcode.SampleMcpServer",
"version": "0.4.0-beta",
"transport": {
"type": "stdio"
},
"package_arguments": [
{
"type": "positional",
"value": "mcp"
},
{
"type": "positional",
"value": "start"
}
]
}
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"tool": "nuget-publisher",
"version": "2.1.0",
"build_info": {
"timestamp": "2023-11-15T14:22:00Z",
"pipeline_id": "nuget-build-456"
}
}
}
}

这实际上会指示MCP客户端执行 dnx Knapcode.SampleMcpServer@0.4.0-beta -- mcp start,而不是默认的 dnx Knapcode.SampleMcpServer@0.4.0-beta(当未提供 package_arguments 时)。

具有多个软件包的文件系统服务器

json
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
"name": "io.github.modelcontextprotocol/filesystem",
"description": "实现文件系统操作的Model Context Protocol (MCP)的Node.js服务器。",
"status": "active",
"repository": {
"url": "https://github.com/modelcontextprotocol/servers",
"source": "github",
"id": "b94b5f7e-c7c6-d760-2c78-a5e9b8a5b8c9"
},
"version": "1.0.2",
"packages": [
{
"registry_type": "npm",
"registry_base_url": "https://registry.npmjs.org",
"identifier": "@modelcontextprotocol/server-filesystem",
"version": "1.0.2",
"transport": {
"type": "stdio"
},
"package_arguments": [
{
"type": "位置参数",
"value_hint": "目标目录",
"description": "访问路径",
"default": "/Users/username/Desktop",
"is_required": true,
"is_repeated": true
}
],
"environment_variables": [
{
"name": "LOG_LEVEL",
"description": "日志级别(debug, info, warn, error)",
"default": "info"
}
]
},
{
"registry_type": "oci",
"registry_base_url": "https://docker.io",
"identifier": "mcp/filesystem",
"version": "1.0.2",
"transport": {
"type": "stdio"
},
"runtime_arguments": [
{
"type": "命名",
"description": "将一个卷挂载到容器中",
"name": "--mount",
"value": "type=bind,src={source_path},dst={target_path}",
"is_required": true,
"is_repeated": true,
"variables": {
"source_path": {
"description": "主机上的源路径",
"format": "文件路径",
"is_required": true
},
"target_path": {
"description": "挂载到容器内的路径。路径应根植于`/project`目录。",
"is_required": true,
"default": "/project"
}
}
}
],
"package_arguments": [
{
"type": "位置参数",
"value_hint": "目标目录",
"value": "/project"
}
],
"environment_variables": [
{
"name": "LOG_LEVEL",
"description": "日志级别(debug, info, warn, error)",
"default": "info"
}
]
}
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"tool": "ci-publisher",
"version": "3.2.1",
"build_info": {
"commit": "a1b2c3d4e5f6789",
"timestamp": "2023-12-01T10:30:00Z",
"pipeline_id": "filesystem-build-789",
"environment": "production"
}
}
}
}

远程服务器示例

{
"name": "io.modelcontextprotocol.anonymous/mcp-fs",
"description": "云托管的MCP文件系统服务器",
"repository": {
"url": "https://github.com/example/remote-fs",
"source": "github",
"id": "xyz789ab-cdef-0123-4567-890ghijklmno"
},
"version": "2.0.0",
"remotes": [
{
"type": "sse",
"url": "http://mcp-fs.anonymous.modelcontextprotocol.io/sse"
}
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"tool": "云部署工具",
"version": "2.4.0",
"build_info": {
"commit": "f7e8d9c2b1a0",
"timestamp": "2023-12-05T08:45:00Z",
"deployment_id": "remote-fs-deploy-456",
"region": "us-west-2"
}
}
}
}

Python包示例

{
"name": "io.github.example/weather-mcp",
"description": "Python MCP服务器,用于天气数据访问",
"repository": {
"url": "https://github.com/example/weather-mcp",
"source": "github",
"id": "def456gh-ijkl-7890-mnop-qrstuvwxyz12"
},
"version": "0.5.0",
"packages": [
{
"registry_type": "pypi",
"registry_base_url": "https://pypi.org",
"identifier": "weather-mcp-server",
"version": "0.5.0",
"runtime_hint": "uvx",
"transport": {
"type": "stdio"
},
"environment_variables": [
{
"name": "WEATHER_API_KEY",
"description": "天气服务的API密钥",
"is_required": true,
"is_secret": true
},
{
"name": "WEATHER_UNITS",
"description": "温度单位(摄氏度、华氏度)",
"default": "摄氏度"
}
]
}
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"tool": "poetry-publisher",
"version": "1.8.3",
"build_info": {
"python_version": "3.11.5",
"timestamp": "2023-11-28T16:20:00Z",
"build_id": "pypi-weather-123",
"dependencies_hash": "sha256:a9b8c7d6e5f4"
}
}
}
}

NuGet (.NET) 包示例

dnx` 工具从 .NET 10 SDK 的 Preview 6 版本开始随附提供。

{
"name": "io.github.joelverhagen/knapcode-samplemcpserver",
"description": "示例 NuGet MCP 服务器,用于生成随机数和随机天气",
"repository": {
"url": "https://github.com/joelverhagen/Knapcode.SampleMcpServer",
"source": "github",
"id": "example-nuget-id-0000-1111-222222222222"
},
"version": "0.5.0",
"packages": [
{
"registry_type": "nuget",
"registry_base_url": "https://api.nuget.org",
"identifier": "Knapcode.SampleMcpServer",
"version": "0.5.0",
"runtime_hint": "dnx",
"transport": {
"type": "stdio"
},
"environment_variables": [
{
"name": "WEATHER_CHOICES",
"description": "用逗号分隔的天气描述列表,供随机选择。",
"is_required": true,
"is_secret": false
}
]
}
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"tool": "dotnet-publisher",
"version": "8.0.100",
"build_info": {
"dotnet_version": "8.0.0",
"timestamp": "2023-12-10T12:15:00Z",
"configuration": "Release",
"target_framework": "net8.0",
"build_number": "20231210.1"
}
}
}
}

复杂的 Docker 服务器及多个参数

json
{
"name": "io.github.example/database-manager",
"description": "用于数据库操作的MCP服务器,支持多种数据库类型",
"repository": {
"url": "https://github.com/example/database-manager-mcp",
"source": "github",
"id": "ghi789jk-lmno-1234-pqrs-tuvwxyz56789"
},
"version": "3.1.0",
"packages": [
{
"registry_type": "oci",
"registry_base_url": "https://docker.io",
"identifier": "example/database-manager-mcp",
"version": "3.1.0",
"transport": {
"type": "stdio"
},
"runtime_arguments": [
{
"type": "named",
"name": "--network",
"value": "host",
"description": "使用主机网络模式"
},
{
"type": "named",
"name": "-e",
"value": "DB_TYPE={db_type}",
"description": "要连接的数据库类型",
"is_repeated": true,
"variables": {
"db_type": {
"description": "数据库类型",
"choices": [
"postgres",
"mysql",
"mongodb",
"redis"
],
"is_required": true
}
}
}
],
"package_arguments": [
{
"type": "named",
"name": "--host",
"description": "数据库主机",
"default": "localhost",
"is_required": true
},
{
"type": "named",
"name": "--port",
"description": "数据库端口",
"format": "number"
},
{
"type": "positional",
"value_hint": "database_name",
"description": "要连接的数据库名称",
"is_required": true
}
],
"environment_variables": [
{
"name": "DB_USERNAME",
"description": "数据库用户名",
"is_required": true
},
{
"name": "DB_PASSWORD",
"description": "数据库密码",
"is_required": true,
"is_secret": true
},
{
"name": "SSL_MODE",
"description": "SSL连接模式",
"default": "prefer",
"choices": [
"disable",
"prefer",
"require"
]
}
]
}
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"tool": "docker-buildx",
"version": "0.12.1",
"build_info": {
"docker_version": "24.0.7",
"timestamp": "2023-12-08T14:30:00Z",
"platform": "linux/amd64,linux/arm64",
"registry": "docker.io",
"image_digest": "sha256:1a2b3c4d5e6f7890"
}
}
}
}

具有远程和软件包选项的服务器

{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
"name": "io.modelcontextprotocol.anonymous/hybrid-mcp",
"description": "MCP server available as both local package and remote service",
"repository": {
"url": "https://github.com/example/hybrid-mcp",
"source": "github",
"id": "klm012no-pqrs-3456-tuvw-xyz789abcdef"
},
"version": "1.5.0",
"packages": [
{
"registry_type": "npm",
"registry_base_url": "https://registry.npmjs.org",
"identifier": "@example/hybrid-mcp-server",
"version": "1.5.0",
"runtime_hint": "npx",
"transport": {
"type": "stdio"
},
"package_arguments": [
{
"type": "named",
"name": "--mode",
"description": "Operation mode",
"default": "local",
"choices": [
"local",
"cached",
"proxy"
]
}
]
}
],
"remotes": [
{
"type": "sse",
"url": "https://mcp.anonymous.modelcontextprotocol.io/sse",
"headers": [
{
"name": "X-API-Key",
"description": "API key for authentication",
"is_required": true,
"is_secret": true
},
{
"name": "X-Region",
"description": "Service region",
"default": "us-east-1",
"choices": [
"us-east-1",
"eu-west-1",
"ap-southeast-1"
]
}
]
},
{
"type": "streamable-http",
"url": "https://mcp.anonymous.modelcontextprotocol.io/http"
}
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"tool": "hybrid-deployer",
"version": "1.7.2",
"build_info": {
"timestamp": "2023-12-03T11:00:00Z",
"deployment_strategy": "blue-green",
"npm_version": "10.2.4",
"node_version": "20.10.0",
"service_endpoints": {
"sse": "deployed",
"streamable": "deployed"
}
}
}
}
}

MCP捆绑包(MCPB)示例

{
"name": "io.modelcontextprotocol/text-editor",
"description": "MCP 捆绑服务器,提供高级文本编辑功能",
"repository": {
"url": "https://github.com/modelcontextprotocol/text-editor-mcpb",
"source": "github"
},
"version": "1.0.2",
"packages": [
{
"registry_type": "mcpb",
"registry_base_url": "https://github.com",
"identifier": "https://github.com/modelcontextprotocol/text-editor-mcpb/releases/download/v1.0.2/text-editor.mcpb",
"version": "1.0.2",
"file_sha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
"transport": {
"type": "stdio"
}
}
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"tool": "mcpb-publisher",
"version": "1.0.0",
"build_info": {
"timestamp": "2023-12-02T09:15:00Z",
"bundle_format": "mcpb-v1"
}
}
}
}

此示例展示了一个MCPB(MCP Bundle)包,该包:

  • 托管在GitHub Releases(一个被列入许可名单的提供者)
  • 包含用于完整性验证的SHA-256哈希值
  • 可以被支持MCPB的MCP客户端直接下载和执行

在CLI工具中嵌入MCP

某些CLI工具捆绑了一个MCP服务器,但没有单独的MCP包或公共仓库。在这些情况下,可以通过指向主机CLI包并提供package_argumentsruntime_hint(如有需要)来启动MCP服务器,从而复用现有的packages结构。

{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
"name": "io.snyk/cli-mcp",
"description": "MCP server provided by the Snyk CLI",
"status": "active",
"version": "1.1298.0",
"packages": [
{
"registry_type": "npm",
"registry_base_url": "https://registry.npmjs.org",
"identifier": "snyk",
"version": "1.1298.0",
"transport": {
"type": "stdio"
},
"package_arguments": [
{ "type": "positional", "value": "mcp" },
{
"type": "named",
"name": "-t",
"description": "Transport type for MCP server",
"default": "stdio",
"choices": ["stdio", "sse"]
}
]
}
]
}

具有自定义安装路径的服务器

对于遵循自定义安装路径的MCP服务器或嵌入在没有独立软件包的应用程序中的服务器,请使用 website_url 字段引导用户查看设置文档:

{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
"name": "io.modelcontextprotocol.anonymous/embedded-mcp",
"description": "MCP server embedded in a Desktop app",
"status": "active",
"website_url": "https://anonymous.modelcontextprotocol.io/embedded-mcp-guide",
"version": "0.1.0"
}

已弃用的服务器示例

json
{
"name": "io.github.example/old-weather",
"description": "遗留的天气服务 - 已弃用:对于新项目请使用 weather-v2",
"status": "已弃用",
"repository": {
"url": "https://github.com/example/old-weather",
"source": "github",
"id": "legacy-abc123-def456-789012-345678-901234567890"
},
"version": "0.9.5",
"packages": [
{
"registry_type": "npm",
"registry_base_url": "https://registry.npmjs.org",
"identifier": "@legacy/old-weather-server",
"version": "0.9.5",
"transport": {
"type": "stdio"
},
"environment_variables": [
{
"name": "WEATHER_API_KEY",
"description": "天气 API 密钥",
"is_required": true,
"is_secret": true
}
]
}
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"tool": "遗留发布工具",
"version": "0.8.1",
"build_info": {
"timestamp": "2023-06-15T09:30:00Z",
"deprecation_notice": "此发布工具已弃用。对于新项目请使用 npm-publisher v2.0+。",
"maintenance_mode": true,
"final_version": true
}
}
}
}

MCP注册表(2025-09-08)—— 介绍MCP注册表

· 阅读需 6 分钟

翻译自:https://blog.modelcontextprotocol.io/posts/2025-09-08-mcp-registry-preview/

今天,我们推出了模型上下文协议(MCP)注册表——一个公开的目录和API,用于收集公开可用的MCP服务器以提高其可发现性和实施效率。通过标准化服务器的分发和发现方式,我们正在扩展其覆盖范围,同时使客户端更容易连接。

MCP注册表现已提供预览版。要开始使用:

MCP服务器的单一事实来源

2025年3月,我们分享了想要为MCP生态系统构建一个中央注册表的计划。今天,我们宣布正式启动 https://registry.modelcontextprotocol.io,作为官方的MCP注册表。作为MCP项目的一部分,MCP注册表以及一个父OpenAPI规范都是开源的——这使每个人都能构建兼容的子注册表。

我们的目标是规范服务器的分配和发现方式,提供一个子注册表可以依赖的主要事实来源。进而,这将扩大服务器的覆盖范围,并帮助客户端更轻松地在MCP生态系统中找到服务器。

公共和私人子注册表

在建立一个中央注册表时,我们认为重要的是不要削弱社区和公司已经构建的现有注册表的功能。MCP 注册表作为公开可用的 MCP 服务器的主要可信来源,组织可以选择基于自定义标准创建子注册表。例如:

公共子注册中心(如与每个MCP客户端相关的带有明确见解的“MCP市场”)可以自由地补充和增强它们从上游MCP注册中心获取的数据。每个MCP终端用户角色都会有不同的需求,由MCP客户端市场以不同的见解方式适当服务其终端用户。

私有子注册表 将存在于具有严格隐私和安全要求的企业内部,但 MCP 注册表为这些企业提供了一个可以构建的单一上游数据源。至少,我们希望与这些私有实现共享 API 架构,以便相关的 SDK 和工具能够在整个生态系统中共享。

在两种情况下,MCP注册表都是起点——它是一个集中化的位置,MCP服务器维护者在这里发布和维护他们自我报告的信息,以供这些下游消费者处理并交付给他们的最终用户。

社区驱动的审核机制

MCP 注册表是一个由注册表工作组维护的官方 MCP 项目,并采用宽松的许可协议。社区成员可以提交问题,标记违反 MCP 管理指南 的服务器,例如包含垃圾信息、恶意代码或冒充合法服务的服务器。然后,注册表维护者可以将这些条目加入拒绝名单,并追溯性地将它们从公共访问中移除。

入门

要开始:

此MCP注册表的预览版旨在帮助我们在正式发布前改进用户体验,但不提供数据持久性保证或其他担保。我们建议MCP的采用者密切关注开发进展,因为在注册表正式发布之前可能会发生重大变更。

随着我们继续开发注册表,我们鼓励在modelcontextprotocol/registry GitHub 仓库上提供反馈和贡献:讨论、问题和拉取请求都非常欢迎。

感谢MCP社区

MCP 注册表从一开始就是一项协作努力,我们非常感谢更广泛开发者社区的热情和支持。

2025年2月,MCP的创作者David Soria ParraJustin Spahr-Summers提出建造一个集中化的社区注册表,并邀请PulseMCPGoose团队参与开发,这一项目作为一个基层倡议逐步展开。PulseMCP 的注册表维护者 Tadas Antanavicius领导了初期工作,并与来自 BlockAlex Hancock合作。他们的工作迅速得到注册表维护者 Toby Padilla 的支持,他是 GitHub 的MCP负责人。而近日,AnthropicAdam Jones也加入了团队成为注册表维护者,推动项目的顺利发布。MCP注册表开发的初步公告中提到,该项目共有16位贡献人员来自至少9家不同的公司。

许多其他人也为这个项目的实现做出了重要贡献:Radoslav Dimitrov 来自 StacklokAvinash Sridhar 来自 GitHubConnor Peet 来自 VS CodeJoel Verhagen 来自 NuGetPreeti Dewani 来自 Last9Avish Porwal 来自 MicrosoftJonathan Hefner,以及许多来自 Anthropic 和 GitHub 的员工,他们提供了代码审查和开发支持。我们还感谢 Registry 贡献者日志 上的每一位,以及参与 讨论和问题 的所有人。

我们深深感谢每一位为这一基础性开源基础设施投入的人。我们携手合作,正在帮助全球的开发者和组织构建更加可靠、具有上下文感知的人工智能应用程序。代表MCP社区,向您致以谢意。

MCP注册表(2025-09-08)—— 审核指南

· 阅读需 3 分钟

翻译自:https://github.com/modelcontextprotocol/registry/blob/main/docs/guides/administration/moderation-guidelines.md

官方MCP注册表中服务器发布者指南。

太长不看

我们非常宽容!我们只会移除非法内容、恶意软件、垃圾信息以及完全无法运行的服务器。

我们不对我们的审核作出任何保证,各子注册机构应将我们的数据“按原样”接受,假定几乎没有或完全没有审核。

范围

这些指南适用于官方MCP注册表 registry.modelcontextprotocol.io

子注册中心可能拥有自己的审核政策。如果您对某个特定子注册中心的内容有疑问,请直接联系他们。

免责声明

我们拥有有限的主动审核能力,这是一个由社区支持的项目。我们主要依赖于上游包注册表(例如 NPM、PyPi 和 Docker)或下游子注册表(例如 GitHub MCP Registry)进行更深入的审核。

这意味着注册表中可能存在一些内容需要根据这些指南移除,而我们尚未移除。您应该相应地处理注册表数据。

我们移除的内容

我们将移除包含以下内容的服务器:

  • 非法内容,包括淫秽内容、版权侵犯和黑客工具。
  • 恶意软件,无论意图如何
  • 垃圾信息,特别是扰乱注册表的大量创建的服务器。示例:
    • 同一台服务器被以不同名称多次提交。
    • 服务器只是提供一个固定的响应,其中包含一些营销文案。
    • 服务器描述充满了营销文案,其实现与其名称或描述无关。
  • 无法正常运行的服务器

我们不移除的内容

一般来说,我们认为应该保持注册表开放,并将管理工作交由子注册表处理。因此,我们不会移除以下类型的服务器:

  • 低质量或存在问题的服务器
  • 具有安全漏洞的服务器
  • 与其他服务器做同样的事情。
  • 提供或包含成人内容

移除如何起作用

当我们移除一台服务器时:

  • 它被设置为“已删除”状态,但仍可通过API访问。
  • 这允许子注册表将其从索引中移除。
  • 在极端情况下,我们可能会覆盖或删除服务器的详细信息,例如当元数据本身是非法时。

上诉;吸引;呼吁(根据具体语境翻译)。

觉得我们弄错了吗?请在我们的GitHub 仓库上提交一个问题,并附上以下内容:

  • 您服务器的ID和名称
  • 您认为它不符合上述移除标准的原因

对此政策的更改

我们仍在学习如何最好地运营MCP注册表!因此,我们未来可能会对这一政策进行修改。