mcpField
S
SayDo

SayDo

本地服务
github.com开发工具08 周前更新

关于 SayDo

SayDo 检查工具是否按声明运行,并为每次运行生成可验证凭据。它面向工具发布者和评估者,提供符合性证据,并区分失败与未能证明任何行为的运行。

主要功能

  1. 01依据签名声明测试工具
  2. 02在本地或沙箱中运行工具
  3. 03将结果标记为通过、失败或未覆盖
  4. 04生成哈希链式签名凭据
  5. 05检测不同运行间的行为变化
  6. 06在独立浏览器页面验证凭据

适用场景

  1. 01以工具符合性检查作为 CI 构建门槛
  2. 02使用或不使用自有声明检查 MCP 服务
  3. 03评估外发数据是否取决于工具输入
  4. 04无需信任凭据签发者即可审查凭据

MCP 相关

SayDo 可测试 MCP 服务,包括从 npm 和 PyPI 发现的服务。它依据声明运行工具,并将运行所能证明的内容记录为可验证凭据。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"vince-gonzalez-saydo": {
"command": "uvx",
"args": [
"saydo"
]
}
}
}
启动命令uvx saydo

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypisaydo0.1.1

工具

4

收录的工具列表 · 更新于 3 周前

此工具无需参数。

详细介绍

╔════════════════════════════════════════════════════════════════════════════════════════════╗
║                                                                                            ║
║                         ███████╗ █████╗ ██╗   ██╗██████╗  ██████╗                          ║
║                         ██╔════╝██╔══██╗╚██╗ ██╔╝██╔══██╗██╔═══██╗                         ║
║                         ███████╗███████║ ╚████╔╝ ██║  ██║██║   ██║                         ║
║                         ╚════██║██╔══██║  ╚██╔╝  ██║  ██║██║   ██║                         ║
║                         ███████║██║  ██║   ██║   ██████╔╝╚██████╔╝                         ║
║                         ╚══════╝╚═╝  ╚═╝   ╚═╝   ╚═════╝  ╚═════╝                          ║
║                                                                                            ║
║                               does the tool do what it says                                ║
║                                                                                            ║
╚════════════════════════════════════════════════════════════════════════════════════════════╝

草稿内容——待所有者审核。 以下内容均为测量结果或机械事实,不包含任何营销宣传,但措辞尚未获批。

发布者在一份经过签名、机器可读的契约中,明确声明工具的具体功能。测试框架负责运行工具,并根据契约对其进行检查。每次运行都会生成一份哈希链式收据,任何人都可以在浏览器中验证,无需账户,也无需信任签发者。

它要回答的问题刻意限定得很窄:这个工具是否按其所述行事? 不是它是否安全,也不是它是否出色。一个拒绝执行操作的工具并不能因此被证明是安全的——它什么都没被证明,而 SayDo 会如实说明这一点,而不是给出绿色对勾。

可能的判定结果

pass          本次运行中,声明的行为得到了验证
fail          工具做了它声明不会做的事
not-covered   本次运行无法得出任何结论

not-covered 绝不会悄悄升级为 pass。这个领域里,大多数值得关注的失败都源于把沉默误当成良好行为。

而一次完整运行的结果有四种,而非两种:

CONFORMANT      工具按其所述行事,且有证据证明
NOT CONFORMANT  工具做了它声明不会做的事
INCONCLUSIVE    未发现失败,但也未能证实任何内容
(已撤销 / 已过期,等注册表发出判定后)

INCONCLUSIVE 承担了大部分判定责任。一个拒绝所有调用的服务——公开扫描中最常见的情况——不会发出网络请求、写入文件或启动进程,因此,简单的测试框架会将这三项全部标记为通过,并为一个什么都没做的程序颁发合规证明。SayDo 要求在这些检查中的任何一项通过之前,运行必须展示出实际行为;服务对自身的描述不算行为。

在 CI 中使用

- uses: vince-gonzalez/saydo@main
  with:
    command: python -m my_server
    declaration: saydo.declaration.json

如果工具违反其声明,或者运行未能证实任何内容,构建都会失败——因为一个无法证明任何事实的绿色检查,比没有检查还糟糕;它会宣称符合要求,却没有人真正作出过这种判定。设置 require-coverage: false 可允许此类构建通过;收据仍会将结果记录为 inconclusive,绝不会将其记为合规证明。

在这里,工具的价值最大。覆盖情况取决于工具是否实际执行其工作,而这需要它接受的凭据和输入——这些东西存在于发布者自己的 CI 中,几乎不会出现在其他地方。

在任何工具上运行

saydo 会对单个工具进行测试,并生成一份收据。工具不必属于你,也不必是 F-Keys 的工具:

python cli/saydo.py verify --npm  @modelcontextprotocol/server-memory --sandbox
python cli/saydo.py verify --pypi mcp-server-time --sandbox
python cli/saydo.py verify --command "python my_server.py" --declaration mine.json

使用 --declaration 时,测试对象是作者自己的契约。不提供该参数时,系统会推断一份刻意保守的契约,大多数不变量都会得到 not-covered——对于一个没有凭据、只能试探陌生服务的人来说,这才是诚实的结果。

--sandbox 会在容器中运行服务,容器唯一的外部通道是一个记录流量的代理(Linux 和 Docker;隔离能力已在 CI 中得到验证,tools/SANDBOX.md 明确说明其覆盖范围和局限)。不使用该参数时,运行只会被观测,而不会受到强制约束,收据会标记为 observed,因此任何声明都不会超出生成该声明的机制所能证明的范围。

然后自行检查结果:

python cli/saydo.py status certivl        # Agent 读取的一次性判定结果
python cli/saydo.py selfcheck             # 证明测试框架能够判定失败

打开 verifier/index.html,粘贴收据和锚点,页面便会在你的浏览器中重新计算完整的链和签名。不会有任何请求离开浏览器。

四层

1  声明         发布者承诺的内容,已签名  (spec/)
2  执行环境     让工具运行并进行监控      (tools/harness.py)
3  回执         哈希链式、已签名、可验证   (tools/receipt.py)
4  注册表       当前仍然有效的内容及其有效期 (tools/registry.py)

第 4 层之所以存在,是因为回执只记录某一时刻的事实,单独看它会回答错误的问题。选择工具的人不会想知道它在 8 月的表现。因此,声明会在读取时过期,这样就不会仅仅因为没人重新运行测试,就一直显示为绿色;撤销也具有持续效力:在某项已撤回的声明上发布更新的通过回执,只会记录这次尝试,不会解除撤回。

这里有两项不属于惯常做法

反事实测试。 观察工具发出网络请求,只能证明它发出了请求,无法说明你的数据是否离开了。因此,工具会使用不同的输入运行两次,并根据各个目的地收到的内容是否随输入变化来分类:input-dependent 表示负载携带了你提供的内容,input-independent 表示没有,无法检查的正文则报告为 unexamined,而不是判定为干净。它把你的输入发送到了 example.com 与 它发出了请求 是两种不同的说法,只有第一种对做决定的人有意义。

seeded/leakserver.py 展示了这一点;它之所以存在,是因为此处曾提出这种说法,却没有进行测试。两个工具每次调用都会联系同一主机。submit 会提交收到的文本;ping 则会提交固定正文:

FAIL  network.none     egress: ping->example.com; submit->example.com
FAIL  data.stays-put   工具将自己的输入数据发送了出去:
                       submit -> example.com。通过在运行之间更改
                       输入确认:example.com 是 input-dependent

network.none 能看到两者,却无法区分它们;观察流量只能做到这一点。data.stays-put 只指出了 submit。

让这项能力成为现实的修正。 在 2026-08-31 之前,标记只写入环境变量 SAYDO_CANARY,唯一曾将它发送出去的测试夹具也从那里读取——这正是已有方案采用的机制,并非另一种机制。没有任何东西把标记放进工具参数中,因此代理在 279 个第三方服务中搜索一个从未出现在任何输入里的字符串,结果 279 个全部都是 unexamined。现在,标记会被放入每次运行收到的参数中,每次运行一个;这才是上面那段文字一直声称的做法。

漂移。 在版本号未变的情况下重新定义工具,在任何单份回执中看起来都完全正常;只有比较两次运行,才能发现这种欺骗。因此,回执会与前一份回执组成链;如果定义发生变化但版本号没有变化,就会被评为 serious,而不是仅作记录。

这项做法并不罕见;在认真了解业界情况之前,这里曾把它说成罕见——详见下文的相关工作。askalf/truecopy 会通过内容哈希固定经过审查的工具定义,并在字节发生变化时让 CI 运行失败;这换个名字就是漂移检测,而且它更早就已发布。与最初看起来相比,这里的独特主张范围更窄:上述反事实测试,以及依据作者编写的契约测试行为,而不只是检查字节是否与经过审查的版本一致。

布局

spec/                  声明模式及其字段语义
tools/harness.py       一致性测试工具      (tools/HARNESS.md)
tools/runner.py        本地与隔离执行      (tools/SANDBOX.md)
tools/egress_proxy.py  记录代理、允许列表强制执行、TLS 检查
tools/canary.py        标记生成与正文检查
tools/differential.py  双运行反事实分类器
tools/drift.py         自上次回执以来的变化
tools/registry.py      过期与永久撤销
tools/status.py        回执 -> Agent 读取的精简对象
tools/declare.py       根据观测到的运行起草声明
tools/discover.py      在 npm 和 PyPI 上查找 MCP 服务
tools/sweep_scale.py   分批测量其中许多服务
action.yml             将 SayDo 作为 GitHub Action
verifier/index.html    自包含的浏览器验证器,无需网络
seeded/malserver.py    一个专门说谎的服务,用于测试测试工具
seeded/silentserver.py 一个拒绝所有请求的服务,用于测试测试工具能否将
                       沉默报告为干净

验证验证器

selfcheck 会运行一个植入种子的服务,该服务会故意违背自己的六项承诺。如果测试工具报告它符合要求,或将发现归因于错误的不变量,那么测试工具就有问题,它曾生成的任何回执都毫无价值。声明验证器也会以同样的方式进行检查,测试它必须拒绝的五种变更。

另一个测试样例恰恰相反,也同样重要:它会启动、列出自己的工具,并拒绝每次调用。测试工具必须将其报告为 INCONCLUSIVE。当它报告为 CONFORMANT 时——它确实如此,而且回执也已签名——这个标记毫无意义,因为大多数公开 MCP 服务在陌生人调用它们时都会这样做。

CI 会在库和 Action 上都断言这两种情况:遇到说谎的工具时,构建必须失败;遇到没有任何结果的运行时,构建也必须失败。

如果一项检查无法因你所关心的原因而失败,那它就不算检查。

本项目借鉴的先行成果

  • TBOM v1.0.2,Jason M. Lovell,2026 — 10.5281/zenodo.18459260
  • CTMS 1.0,George Kanellopoulos,2026 — github.com/gkanellopoulos/ctms

两者都是来源证明:它们确立工具元数据就是发布者发布的内容,也都声明根据该元数据验证行为不在其范围之内。本仓库正是为了填补这一范围之外的空缺。SayDo 声明通过 TBOM 现有的 attestations[] 附加,并复用其 ToolDigest 格式,因此这里的内容不会分叉出它们中的任何一个。

相关工作,以及界限在哪里

这一节不断变长,这正是它有用的地方。这里的某项内容被描述为不寻常之后,我们才发现了下面这些项目,因此,这份列表也记录了那些不得不收窄的说法。

  • Pipelock,github.com/luckyPipewrench/pipelock — 一种 Agent 防火墙,可调解运行中 Agent 的流量,并生成签名回执。
  • truecopy,github.com/askalf/truecopy — 审查工具定义,使用内容哈希固定其版本,并可选用 Ed25519 签名;字节发生变化时会让 CI 失败。与 redstamp 搭配用于运行时遏制。
  • Proofpane,github.com/Proofpane/releases — 一种治理代理,会将每次工具调用记录到哈希链审计日志中,并导出为由 Ed25519 签名、可离线验证的证据包。闭源;该仓库是下载镜像。
  • dcl-webhook,github.com/Fronesis-Labs/dcl-webhook — 将策略判定写入防篡改的 SHA-256 哈希链。

界限不在于密码学。哈希链、签名且可离线验证的记录如今已是常见设计,本仓库并未声称这是自己的独创。

界限在于记录的内容。Pipelock 和 Proofpane 为已部署 Agent 执行过的操作提供证明。truecopy 证明工具与你审查过的字节完全相同——这属于完整性,可发现静默更新,但无法说明一个始终行为不当的工具。SayDo 回执则证明某个工具的某个版本确实按其作者所说的那样工作;这是通过运行该工具,并依据运行前编写的契约进行测试得出的。

完整性问的是这是不是我批准过的东西? 符合性问的是这东西是否按其声称的那样工作? 两者都值得拥有,且都无法回答对方的问题。

根据目前收集到的证据,似乎仍未见他人采用的做法是:通过两次运行的反事实比较,区分你的数据已离开与发生了一次请求;以及在工具仅仅拒绝执行操作时,不把它判定为干净。如果其中任何一项已有先例,就应将其列入此列表,并删去上面的说法,而不是为其辩护。

这里曾有一周把第一项说得过头了。当时给出的与 Pipelock 的区别是他们的金丝雀是环境变量,我们的则是工具自身的输入——而我们的也确实是环境变量。截至 2026-08-31,它已是工具的输入,并附有一个展示该论点的测试样例。让这句话保持准确的是这个测试样例,而不是本段文字。

状态

公开的工作仓库。此处生成的回执使用由 F-Keys Creative LLC 持有的概念验证密钥签名;生产环境的签名方案尚未确定。语料库测量结果仍是草案,等待所有者审核,其中的每项数据都是下限——服务器是在无凭据的情况下运行的,因此有凭据的服务器可能会做出记录之外的操作。

许可:开放层——声明模式和规范文本(spec/)、参考工具(tools/)、浏览器验证器(verifier/)以及带种子的测试样例(seeded/)——采用 Apache-2.0 许可(参见 LICENSE、NOTICE)。本仓库不会为签名的公开制品授予任何许可。


╔════════════════════════════════════════════════════════════╗
║                                                            ║
║      ███████╗      ██╗  ██╗███████╗██╗   ██╗███████╗       ║
║      ██╔════╝      ██║ ██╔╝██╔════╝╚██╗ ██╔╝██╔════╝       ║
║      █████╗  █████╗█████╔╝ █████╗   ╚████╔╝ ███████╗       ║
║      ██╔══╝  ╚════╝██╔═██╗ ██╔══╝    ╚██╔╝  ╚════██║       ║
║      ██║           ██║  ██╗███████╗   ██║   ███████║       ║
║      ╚═╝           ╚═╝  ╚═╝╚══════╝   ╚═╝   ╚══════╝       ║
║                                                            ║
║               ·   C  R  E  A  T  I  V  E   ·               ║
║                                                            ║
║          ────────────────────────────────────────          ║
║                                                            ║
║                      Vincent Gonzalez                      ║
║                         f-keys.com                         ║
║                 ORCID 0009-0005-3640-014X                  ║
║                                                            ║
╚════════════════════════════════════════════════════════════╝

属于 F-Keys —— 独立的硬件、软件和互联网产品。请参阅工作日志和实时状态。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。