Skip to main content
Glama

groundtruth-mcp

ci pypi python license

あなたのコーディングエージェントは、リポジトリ内のすべてのファイルを読めるにもかかわらず、まだ推測しているだけです。 これは、プロジェクト自身のチェック、リプレイ、シミュレーション、クエリをMCPツールに変換し、エージェントが編集の結果を予測するのではなく、観察できるようにします。

中国語ドキュメント · 導入ガイド · アーキテクチャ · 固定シードの理由


問題

構造化設定(ワークフローグラフ、ルールファイル、ステートマシン、パイプライン定義)を編集するエージェントは、誤った種類のコンテキストを扱っています。スキーマは読めますが、実際に実行されたときに何が起こるかを読むことはできません。

だから推測するのです。リトライ制限を変更し、その変更は安全だと伝えます。なぜなら「安全」というのが、もっともらしい差分に対して最も確からしい次のトークンだったからです。誰も実際に実行していません。違反した制約は、3ファイル先の不変条件や、ポリシーが最後に調整されて以来誰もサンプリングしていない分布の中にあります。

修正方法は、より優れたプロンプトではありません。エージェントに観察できるものを与えることです。

Related MCP server: MCP Software-Engineering RL Environment

機能

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

あなたが書く4つの小さな関数から構築された5つのツール:

ツール

回答

それを有用にする特性

lint

この設定は自己整合的ですか?

すべての問題が編集すべき正確なパスを伝えます

replay

これを実行すると何が起こりますか?

(config, seed) の純粋関数 — どこでも再現可能

simulate

私の変更は全体的に良くなりますか、悪くなりますか?

シード付きバッチ、分布、閾値、合格/不合格

query

データには実際に何が入っていますか?

正規表現ではなくデータベースによって強制される読み取り専用

describe_data

どのテーブルがありますか?

スキーマを推測する必要がないように

同じ機能はCLIとしても実行できるため、groundtruth simulate --gate は、エージェントが最適化するのと同じ閾値を読み取るマージゲートです。コピーが1つしかないので、それらが乖離することはありません。

60秒

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

同梱の例は、設定駆動のチェックアウトです。4つのページ、不安定な決済ゲートウェイ、リトライポリシー、去っていく顧客。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

そのうち6つはルールファイルに由来します。RETRY_BUDGET_TOO_THIN は8行のPythonから来ています。「このリトライ予算は製品の障害目標を満たしているか」はスキーマではなく算術だからです。

それでは1回の実行を見てみましょう:

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は常にその7つのステップを生成します。あなたのマシンでも、CIでも、来年でも。それがこのツールを読む価値あるものにしています。

そしてその2000回分:

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

真価を発揮する部分

1つの数値を上げます — 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ポイントが失われています。スキーマも型システムもコードレビューもそれを検出できません。宣言された帯域を持つシード付きバッチは、人間が差分を読む前に、プルリクエスト上で4秒で検出します。

逆方向にも機能します。express_checkout は標準フローよりも高い成功率(91.0%)を報告しますが、より悪い設定です。その決済失敗率は0.3%に対して3.9%で、問題なさそうな主要な数値の中に隠れています。集計値はそれを見逃します。手書きのバリデータはそれを明確に指摘します:

[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%)

どちらの層も他方を包含しません。だから2つあるのです。

導入方法

1つのモジュールと1つの設定ファイル。examples/checkout-flow/groundtruth_app.py が全体のテンプレートです — コメントを含めて約100行。

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 の両方が得られます — ライブラリはシードごとにそれを1回実行し、結果を保持します。その他すべて(シードのバッチ処理、集計、パーセンタイル、閾値ゲーティング、出力予算、エラーメッセージの表現、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。

自動的に得られるルール

構造チェックは記述するのではなく宣言します。構造化設定が実際に劣化する方法をそれぞれカバーする12のタイプがあります:

タイプ

検出するもの

主要フィールド

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

1件必要だが空のリスト

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 と報告できるのです。

各ルールはオプションの code、severity、hint を受け取ります。ヒントはエージェントが行動するための文なので、命令形で書いてください。

読み取り専用は読み取り専用

query は1つの SELECT を実行します。それを強制する2つの層がありますが、それらは同等ではありません。

キーワードスキャンはユーザー体験です。DELETE FROM … を、モデルが解読しなければならないデータベースエラーではなく、その旨を伝える文で拒否します。これは境界ではありません — テキストに対するブロックリストは常に1つのケースで間違う可能性があり、その典型例は 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 source

Python 3.11以上。コアにはサードパーティの依存関係がありません — これは意図的で、CIゲートがエージェントスタックに依存しないようにしています。ベアランナーはSDKをインストールせずにあなたの閾値を強制できます。

配線を検証する

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

サーバーを実際のサブプロセスとして起動し、stdioで初期化し、ツールを一覧表示し、そのうち2つを呼び出し、返ってきた内容を出力します — クライアントが実行するのと同じシーケンスです。エージェントがツールを認識しないと非難する前に、これを実行してください。

CIがすべてのプルリクエストで強制すること

「テストが実行された」という意味のバッジではありません — 6つの項目 で、それぞれが何かをブロックしてきました:

チェック

なぜ提案ではなくゲートなのか

ruff check + ruff format --check

BLE を含むため、すべての広範な except には書面による正当化が必要

mypy

パッケージは py.typed を同梱しています。誤ったアノテーションは誤ったAPIです

pytest on 3.11 / 3.12 / 3.13

69テスト、カバレッジ下限75%(現在はブランチカバレッジ込みで78%)

scripts/mcp_smoke.py

実際のサブプロセス、実際のstdio、実際の tools/list と tools/call

simulate --gate --check-determinism

プロジェクト自身の引数を自分自身に適用

lint broken_checkout は必ず終了コード1を返す

失敗できないlintは装飾にすぎない

明確に述べた制限事項

  • SQLテーブルの許可リストはテキストベースです。 FROM と JOIN の後の識別子をスキャンします。実際のテーブル単位の強制はデータベースの権限です。これは優れたエラーメッセージを備えたガードレールであり、実際に効くのは読み取り専用トランザクションです。

  • キーワードブロックリストは文字列リテラル内にも一致します。 grant を含む値でフィルタリングするクエリは拒否されます。修正するには実際のSQLパーサーが必要ですが、パーサーが境界ではないため、構築する価値はありません。

  • 自動 LIMIT はヒューリスティックです。 サブクエリ内の LIMIT はトップレベルの追加を抑制します。max_rows はレンダリングされる内容の上限を依然として設定します。

  • セレクタはフィルタリングしません。 states[].transitions[] はすべてを走査します。states[kind=terminal] はありません。述語言語は、誰も求めていない3番目の機能になるでしょう。代わりに @kit.validator を書いてください。

  • 閾値はプロジェクト全体で、ターゲットごとではありません。 プロジェクト内のすべてのターゲットは同じ帯域で判断されます。設定に本当に異なる帯域が必要なプロジェクトは、別々の groundtruth.toml ファイルにすべきです。

  • PostgreSQLソースは実装されていますが、軽くしか試されていません。 テストスイートは、サービスコンテナなしでどこでも実行できるSQLiteに対して境界を証明しています。

このプロジェクトの由来

パターンが定着した非公開コードベースから抽出されたものです。それは、コントリビューターたちが、スキーマ検証を通過するものの実行時に壊れる設定を出し続けたオーサリングパイプラインです。ドメイン固有の部分は残されました。一般化されたのは、その形——チェック、リプレイ、シミュレート、クエリ——と、機能リスト以上に重要であることが判明した一連の決定事項です:

  • エージェントとCIの両方が読む単一のしきい値リスト。2つのコピーがずれてしまい、ツールはCIが拒否する数値をしばらくPASSと報告していたからです。

  • 有効な代替案をインラインで挙げるエラー。何を渡せるのかを知るために2回目の呼び出しをしなければならないエージェントは、代わりに推測してしまうからです。

  • ライブ設定から構成されるツールの説明。古い説明は、エージェントが誤って、しかも自信を持って使うツールになるからです。

  • すべての経路で出力に上限を設定すること。1つの過剰なクエリが会話の残りを追い出してしまうからです。

docs/ARCHITECTURE.md には、モジュールマップと完全な理由が記載されています。

ライセンス

MIT。

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to search code by meaning, explore codebase structure, store and query knowledge with temporal facts, and read source code through a set of MCP tools.
    310 npm
    7
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables coding agents to perform file, search, patch, git, process, test, package, network, and system operations through 60 typed MCP tools with structured inputs/outputs, structured errors, and a full event journal, replacing terminal use with a typed machine API.
    MIT