groundtruth-mcp
groundtruth-mcp
你的编码智能体可以读取仓库中的每个文件,却仍然是在猜测。 它将你项目自身的检查、回放、模拟和查询转化为 MCP 工具,从而让智能体观察其编辑带来的后果,而不是预测它们。
问题所在
正在编辑结构化配置——工作流图、规则文件、状态机、流水线定义——的智能体,所使用的上下文是错误的类型。它可以读取 schema,却无法读取程序运行时实际发生的事情。
于是它只能推断。它修改了一个重试限制,然后告诉你这个改动是安全的,因为“安全”是在一个看起来合理的 diff 下最有可能的下一个 token。没有人真正运行过任何东西。它所违反的约束,存在于三个文件之外的一个不变量里,或者存在于自上次策略调优以来再没人采样过的分布中。
解决办法不是更好的提示词,而是给智能体一些可以观察的东西。
它的功能
flowchart LR
E[Agent edits a config] --> L[lint]
L -->|DANGLING_TRANSITION at states 1.transitions 0.to| E
E --> R[replay seed=7]
R -->|the 5 steps that actually ran| E
E --> S[simulate 2000 seeds]
S -->|88.3% success · p95 2566ms · PASS| E
S --> G["CI: groundtruth simulate --gate"]
G -->|same config, same thresholds| S五个工具,由你编写的四个小函数构建而成:
工具 | 回答的问题 | 让它有用的属性 |
| 这份配置自洽吗? | 每个问题都带有需要编辑的确切路径 |
| 当我运行这一个时会发生什么? |
|
| 我的改动总体上更好还是更糟? | 带种子的批量、分布、阈值、通过/失败 |
| 数据中实际有什么? | 由数据库强制只读,而不是靠正则表达式 |
| 存在哪些表? | 这样就不必去猜 schema |
同样的能力也可以作为 CLI 运行,因此 groundtruth simulate --gate 就是一个读取智能体所优化的同一套阈值的合并门禁。它们不会漂移,因为只有一份副本。
六十秒上手
pip install "groundtruth-mcp[mcp]"
git clone https://github.com/ZhenGtai123/groundtruth-mcp && cd groundtruth-mcp
groundtruth --config examples/checkout-flow/groundtruth.toml lint broken_checkout内置示例是一个由配置驱动的结账流程:四个页面、一个不稳定的支付网关、一套重试策略,以及会流失的顾客。broken_checkout.json 包含了智能体在编辑它无法运行的配置时实际会犯的错误。
broken_checkout: BLOCKED errors=6 warnings=1 infos=0
source: flows\broken_checkout.json
-- ERRORS — these block (6) --
[DANGLING_TRANSITION] states[1].transitions[0].to 'payment_methd' does not name any states.id
fix: point it at an existing state id, or delete the transition
[DEAD_END] states[6] 'review_hold' has no outgoing edge and is not marked terminal — a run that arrives here stops with no result
fix: give it a transition, or mark it kind = "terminal" with an outcome
[DUPLICATE_STATE] states[2] duplicate id='shipping' (first declared at states[1])
fix: rename one of them; the engine silently uses the first and ignores the rest
[RATE_OUT_OF_RANGE] policy.gateway_failure_rate 1.4 is above the maximum 1.0
fix: this is a probability, not a percentage — 0.18, not 18
[RETRY_BUDGET_TOO_THIN] policy.max_retries 140% gateway failure with 1 retries leaves 196.0% of checkouts failing on payment alone (budget: 2.0%)
fix: raise max_retries, or lower gateway_failure_rate if the gateway improved
[UNKNOWN_STATE_KIND] states[3].kind 'stage' is not one of ['step', 'gateway', 'retry', 'terminal']
fix: the engine only knows these four kinds; anything else is treated as a plain step
-- WARNINGS (1) --
[UNREACHABLE_STATE] states[4] 'gift_wrap' cannot be reached from 'cart_review'
fix: no path from start reaches this state — delete it, or wire it in其中六个来自规则文件。RETRY_BUDGET_TOO_THIN 来自八行 Python 代码,因为“这个重试预算是否达到产品的失败目标”是算术问题,而不是 schema。
现在看一次运行:
groundtruth --config examples/checkout-flow/groundtruth.toml replay standard_checkout --seed 3standard_checkout seed=3 outcome=success steps=7 fingerprint=52b66a2024a61b5d
metrics: latency_ms=2506 payment_attempts=2 steps=7
-- TRACE --
0. cart_review --always-->
1. shipping --always-->
2. payment_method --always-->
3. authorize --failure--> # attempt 1 declined
4. retry_decision --retries_left--> # 0 retry(s) used of 2
5. authorize --success--> # attempt 2 authorized
6. confirmed # terminal: success种子 3 总会产生那七步——在你的机器上、在 CI 中、明年也是如此。这就是它值得一读的原因。
再看两千次:
groundtruth --config examples/checkout-flow/groundtruth.toml \
simulate standard_checkout --runs 2000 --seed 0 --gate --check-determinismstandard_checkout: PASS runs=2000 base_seed=0 fingerprint=449e16b50c8184c0
-- OUTCOMES --
success: 1767 (88.3%)
abandoned: 227 (11.3%)
payment_failed: 6 (0.3%)
-- METRICS (mean / p50 / p95 / max) --
latency_ms: 1587.75 / 1553 / 2566 / 3626
payment_attempts: 1.06 / 1 / 2 / 3
steps: 5.12 / 5 / 7 / 10
-- THRESHOLDS --
PASS rate:success = 0.8835 expected >= 0.8 (below this, the flow is losing customers faster than the business case allows)
PASS rate:stuck = 0 expected <= 0 (a run with nowhere to go is always a config bug, never bad luck)
PASS p95:latency_ms = 2566 expected <= 4000 (95th-percentile checkout wall time, retries included)
PASS mean:payment_attempts = 1.0585 expected <= 1.6 (rising attempts mean the gateway is degrading or the retry policy is too eager)
note: determinism: 20 seeds re-ran identically真正体现价值的部分
调高一个数字——shipping.abandon_chance 从 0.05 改为 0.28——这种改动看起来像一次产品调整,并能通过评审:
$ groundtruth lint standard_checkout
standard_checkout: OK errors=0 warnings=0 infos=0 # exit 0
$ groundtruth simulate standard_checkout --runs 2000 --seed 0 --gate
standard_checkout: FAIL runs=2000 base_seed=0 fingerprint=5a7c0d9feed5adca
-- OUTCOMES --
success: 1336 (66.8%)
abandoned: 660 (33.0%)
-- THRESHOLDS --
FAIL rate:success = 0.668 expected >= 0.8
PASS rate:stuck = 0 expected <= 0
PASS p95:latency_ms = 2549 expected <= 4000
PASS mean:payment_attempts = 0.795 expected <= 1.6
# exit 1结构上完美无缺,却少了 21 个百分点的转化率。任何 schema、类型系统或代码评审都抓不到这个问题;而一个带声明区间的种子批次在四秒内就能在拉取请求上发现它,甚至早于人类阅读 diff。
反过来也同样有效。express_checkout 发布的成功率高于标准流程——91.0%——但它其实是更差的配置:其支付失败率为 3.9%,而标准流程只有 0.3%,这个数字隐藏在一个看起来不错的总览数字之下。聚合指标发现不了它;手写的校验器却直截了当地指出了这一点:
[RETRY_BUDGET_TOO_THIN] policy.max_retries 18% gateway failure with 1 retries
leaves 3.2% of checkouts failing on payment alone (budget: 2.0%)这两层并不能互相替代,所以才需要两层。
采用它
一个模块,一个配置文件。examples/checkout-flow/groundtruth_app.py 就是完整的模板——包括注释在内大约一百行。
from groundtruth_mcp import Context, Issue, Loaded, Toolkit, Trace
kit = Toolkit(name="my-project", subject_noun="pipeline")
@kit.loader
def load(name: str):
path = CONFIG_DIR / f"{name}.yaml"
if not path.is_file():
return None # → "no pipeline named X; available: ..."
return Loaded(subject=parse(path), source=str(path))
@kit.validator
def check(pipeline, ctx: Context) -> list[Issue]:
... # the checks a rule file can't express
@kit.runner
def run_once(pipeline, seed: int, ctx: Context) -> Trace:
... # one run, pure in (pipeline, seed)仅 @kit.runner 一个装饰器就能同时提供 replay 和 simulate——库会为每个种子运行一次并保留结果。其余一切(种子批处理、聚合、百分位数、阈值门禁、输出预算、错误措辞、MCP 接口)都由包本身提供。
# groundtruth.toml
[project]
toolkit = "groundtruth_app:kit"
[lint]
rules = "rules.toml"
[[thresholds]]
metric = "rate:success"
min = 0.80
note = "why this number, for whoever has to change it"然后 groundtruth doctor 告诉你哪些已连接,groundtruth serve 把工具交给智能体,groundtruth simulate --gate 阻止合并。完整的分领域示例逐步讲解见:docs/ADOPTION.md。
免费获得的规则
结构检查是声明出来的,而不是手写的。十二种类型,每一种都覆盖了结构化配置实际腐化的一种方式:
类型 | 捕获的问题 | 关键字段 |
| 只写了一半的条目 |
|
| 引擎静默遮蔽的重复 ID |
|
| 你的引擎无法处理的值 |
|
| 本应是数字却放了字符串 |
|
| 在概率字段中出现了 |
|
| 违反命名约定的 ID |
|
| 在需要一个条目的地方出现了空列表 |
|
| 引用了已被重命名的对象 |
|
| 没有从起点出发的路径能到达的节点 |
|
| 没有出口的非终止节点 |
|
| 转换到自身的节点 |
|
| 没有出口的环(对于有意的环,可以用 |
|
选择器是一种刻意保持精简的路径语言——states[].transitions[].to——每次匹配都会报告找到该匹配的具体路径,这正是 states[3].transitions[1].to 能够实现的原因,而不是仅仅报告“某个转换无效”。
每条规则都有可选的 code、severity 和 hint。hint 是智能体将据此行动的句子,所以请用祈使句来写。
只读就是只读
query 只执行一条 SELECT。有两层机制来保证这一点,而它们并不平等。
关键词扫描是为了用户体验:它会在遇到 DELETE FROM … 时用一句话说明原因并拒绝执行,而不是返回一个需要模型去解码的数据库错误。它不是边界——基于文本的黑名单总是只差一个大小写就会出错,最典型的例子是 SELECT * INTO audit_copy FROM users:它以 SELECT 开头,不包含任何被禁止的动词,却创建了一张表。
真正的边界是数据存储:SQLite 上使用 mode=ro 加 PRAGMA query_only,PostgreSQL 上使用 READ ONLY 事务,并且两者都设置了语句超时。测试完全绕过守卫,确认连接仍然拒绝执行。
列脱敏是唯一一个真正算作强制措施的文字层控制:deny_columns 中的值会在获取之后、结果字符串存在之前被丢弃,因此 SELECT * 也无法泄露它们。所有返回的内容都用 <untrusted> 标签包裹,因为一个看起来像指令的 notes 列本身就是数据,必须被标记为数据后再到达。
CLI
groundtruth [--config PATH] <command>
doctor what is wired up, what is missing
targets the configs this project exposes
lint TARGET exit 1 on errors
replay TARGET --seed N one deterministic run, full trace
simulate TARGET --runs N --seed N --gate --check-determinism
query "SELECT ..." one read-only statement
schema readable tables and columns
serve the MCP server, over stdio退出码:0 表示干净,1 表示有发现(lint 错误、阈值超出其区间、非确定性),2 表示无法运行(配置错误、缺少能力、查询被拒绝)。可以为 lint、replay 和 simulate 添加 --json 以输出机器可读的结果。
安装
pip install groundtruth-mcp # core: rules, simulation, gating, CLI
pip install "groundtruth-mcp[mcp]" # + the MCP server
pip install "groundtruth-mcp[postgres]" # + the PostgreSQL data sourcePython 3.11+。核心没有任何第三方依赖——这是有意为之,这样 CI 门禁就不依赖智能体技术栈。一个独立的运行器无需安装 SDK 就能强制执行你的阈值。
验证接线
python scripts/mcp_smoke.py [path/to/groundtruth.toml]它会将服务器作为真正的子进程启动,通过 stdio 初始化,列出工具,调用其中两个,并打印返回结果——与客户端执行的序列完全相同。在指责智能体看不到你的工具之前,先运行它。
CI 对每个拉取请求强制检查什么
这可不是一个“测试已经运行”的徽章——六项检查,每一项都曾经拦截过问题:
检查项 | 为什么它是门禁而不是建议 |
| 包含 |
| 包附带 |
| 69 个测试,覆盖率下限 75%(当前为 78%,含分支覆盖率) |
| 真实的子进程、真实的 stdio、真实的 |
| 项目自己的参数,应用于自身 |
| 不会失败的 lint 只是装饰品 |
限制,直说
SQL 表允许列表是基于文本的。 它会扫描
FROM和JOIN之后的标识符。真正的按表强制是数据库授权;这只是一个带有良好错误信息的护栏,真正起作用的是只读事务。关键词黑名单会在字符串字面量内部进行匹配。 对包含
grant的值进行过滤的查询会被拒绝。修复这个问题需要一个真正的 SQL 解析器,而当解析器不是边界时,不值得构建它。自动
LIMIT是一种启发式方法。 子查询中的LIMIT会抑制顶层追加。max_rows仍然限制渲染的内容。选择器不进行过滤。
states[].transitions[]会遍历所有内容;没有states[kind=terminal]这样的写法。谓词语言会是第三个没人要求的功能。请改用@kit.validator。阈值是项目级的,而不是按目标设置的。 项目中的每个目标都按相同的区间来评判。配置确实需要不同区间的项目,应该使用独立的
groundtruth.toml文件。PostgreSQL 数据源已实现,但测试较少——测试套件针对 SQLite 验证了边界,因为 SQLite 无需服务容器就能在任何地方运行。
这个项目的由来
该模式提取自一个私有代码库,并在那里确立了自己的地位:一个创作管道,其贡献者不断交付通过模式验证却会在运行时崩溃的配置。领域特定的部分留在了原地。被泛化的是其形态——检查、重放、模拟、查询——以及一系列被证明比功能列表更重要的决策:
单一阈值列表,由代理和 CI 共同读取,因为两份副本会漂移,而工具曾一度对 CI 会拒绝的数字报告 PASS。
错误信息中内联列出合法的替代项,因为代理如果必须发起第二次调用才能知道它能传递什么,就会去猜测。
工具描述由实时配置生成,因为过时的描述会让代理错误而自信地使用该工具。
每条路径都限制输出上限,因为一次过度的查询就可能挤掉对话的其余部分。
docs/ARCHITECTURE.md 包含模块地图和完整论证。
许可证
MIT。
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
A paid remote MCP for OpenAI Codex agent coordination MCP, built to return verdicts, receipts, usage
Free public MCP for AI agents — 193 tools, 44 workflows. No API key.
Agent Replay Debugger MCP — record every agent step + deterministic replay. Step-debugger for
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ZhenGtai123/groundtruth-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server