Skip to main content
Glama

groundtruth-mcp

ci pypi python license

你的编码智能体可以读取仓库中的每个文件,却仍然是在猜测。 它将你项目自身的检查、回放、模拟和查询转化为 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

五个工具,由你编写的四个小函数构建而成:

工具

回答的问题

让它有用的属性

lint

这份配置自洽吗?

每个问题都带有需要编辑的确切路径

replay

当我运行这一个时会发生什么?

(config, seed) 的纯函数——在任何地方都可复现

simulate

我的改动总体上更好还是更糟?

带种子的批量、分布、阈值、通过/失败

query

数据中实际有什么?

由数据库强制只读,而不是靠正则表达式

describe_data

存在哪些表?

这样就不必去猜 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 3
standard_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-determinism
standard_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_chance0.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 一个装饰器就能同时提供 replaysimulate——库会为每个种子运行一次并保留结果。其余一切(种子批处理、聚合、百分位数、阈值门禁、输出预算、错误措辞、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

免费获得的规则

结构检查是声明出来的,而不是手写的。十二种类型,每一种都覆盖了结构化配置实际腐化的一种方式:

类型

捕获的问题

关键字段

required_fields

只写了一半的条目

select, fields

unique_key

引擎静默遮蔽的重复 ID

select, key

enum

你的引擎无法处理的值

select, values

type

本应是数字却放了字符串

select, expect

range

在概率字段中出现了 1.4

select, min, max

pattern

违反命名约定的 ID

select, regex

not_empty

在需要一个条目的地方出现了空列表

select

ref_exists

引用了已被重命名的对象

select, collection, key

reachable

没有从起点出发的路径能到达的节点

collection, key, edges, start

no_dead_end

没有出口的非终止节点

collection, key, edges, terminal_field

no_self_loop

转换到自身的节点

collection, key, edges

no_cycle

没有出口的环(对于有意的环,可以用 allow 列表放行)

collection, key, edges

选择器是一种刻意保持精简的路径语言——states[].transitions[].to——每次匹配都会报告找到该匹配的具体路径,这正是 states[3].transitions[1].to 能够实现的原因,而不是仅仅报告“某个转换无效”。

每条规则都有可选的 codeseverityhint。hint 是智能体将据此行动的句子,所以请用祈使句来写。

只读就是只读

query 只执行一条 SELECT。有两层机制来保证这一点,而它们并不平等。

关键词扫描是为了用户体验:它会在遇到 DELETE FROM … 时用一句话说明原因并拒绝执行,而不是返回一个需要模型去解码的数据库错误。它不是边界——基于文本的黑名单总是只差一个大小写就会出错,最典型的例子是 SELECT * INTO audit_copy FROM users:它以 SELECT 开头,不包含任何被禁止的动词,却创建了一张表。

真正的边界是数据存储:SQLite 上使用 mode=roPRAGMA 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 表示无法运行(配置错误、缺少能力、查询被拒绝)。可以为 lintreplaysimulate 添加 --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 source

Python 3.11+。核心没有任何第三方依赖——这是有意为之,这样 CI 门禁就不依赖智能体技术栈。一个独立的运行器无需安装 SDK 就能强制执行你的阈值。

验证接线

python scripts/mcp_smoke.py [path/to/groundtruth.toml]

它会将服务器作为真正的子进程启动,通过 stdio 初始化,列出工具,调用其中两个,并打印返回结果——与客户端执行的序列完全相同。在指责智能体看不到你的工具之前,先运行它。

CI 对每个拉取请求强制检查什么

这可不是一个“测试已经运行”的徽章——六项检查,每一项都曾经拦截过问题:

检查项

为什么它是门禁而不是建议

ruff check + ruff format --check

包含 BLE,因此每个宽泛的 except 都必须有书面理由

mypy

包附带 py.typed;错误的注解就是错误的 API

pytest 在 3.11 / 3.12 / 3.13

69 个测试,覆盖率下限 75%(当前为 78%,含分支覆盖率)

scripts/mcp_smoke.py

真实的子进程、真实的 stdio、真实的 tools/listtools/call

simulate --gate --check-determinism

项目自己的参数,应用于自身

lint broken_checkout 必须退出 1

不会失败的 lint 只是装饰品

限制,直说

  • SQL 表允许列表是基于文本的。 它会扫描 FROMJOIN 之后的标识符。真正的按表强制是数据库授权;这只是一个带有良好错误信息的护栏,真正起作用的是只读事务。

  • 关键词黑名单会在字符串字面量内部进行匹配。 对包含 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。

-
license - not tested
-
quality - not tested
A
maintenance

Maintenance

Maintainers
Response time
Release cycle
1Releases (12mo)
Commit activity

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

View all MCP Connectors

Latest Blog Posts

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