Skip to main content
Glama

groundtruth-mcp

ci pypi python license

Dein Coding-Agent kann jede Datei in deinem Repository lesen und rät trotzdem. Dies verwandelt die eigenen Checks, Replays, Simulationen und Abfragen deines Projekts in MCP-Werkzeuge, sodass er die Konsequenzen seiner Änderung beobachtet, statt sie vorherzusagen.

中文文档 · Einführungsleitfaden · Architektur · Warum feste Seeds


Das Problem

Ein Agent, der eine strukturierte Konfiguration bearbeitet – einen Workflow-Graphen, eine Regeldatei, eine Zustandsmaschine, eine Pipeline-Definition – arbeitet mit der falschen Art von Kontext. Er kann das Schema lesen. Er kann nicht lesen, was passiert, wenn das Ding läuft.

Also inferiert er. Er ändert ein Wiederholungslimit und sagt dir, die Änderung sei sicher, weil „sicher" das plausibelste nächste Token angesichts eines Diffs war, der vernünftig aussah. Niemand hat etwas ausgeführt. Die verletzte Randbedingung lebt in einer Invariante drei Dateien weiter, oder in einer Verteilung, die niemand mehr abgetastet hat, seit die Richtlinie zuletzt angepasst wurde.

Die Lösung ist kein besserer Prompt. Es geht darum, dem Agenten etwas zu geben, das er beobachten kann.

Was es tut

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

Fünf Werkzeuge, gebaut aus vier kleinen Funktionen, die du schreibst:

Tool

Beantwortet

Eigenschaft, die es nützlich macht

lint

Ist diese Konfiguration in sich konsistent?

Jedes Problem trägt den genauen Pfad zum Bearbeiten

replay

Was passiert, wenn ich diesen Lauf ausführe?

Reine Funktion von (config, seed) — überall reproduzierbar

simulate

Ist meine Änderung insgesamt besser oder schlechter?

Seeded Batch, Verteilung, Schwellenwerte, bestanden/nicht bestanden

query

Was steckt tatsächlich in den Daten?

Schreibgeschützt erzwungen durch die Datenbank, nicht durch eine Regex

describe_data

Welche Tabellen existieren?

Damit niemand ein Schema raten muss

Dieselben Fähigkeiten laufen als CLI, also ist groundtruth simulate --gate ein Merge-Gate, das die gleichen Schwellenwerte liest, gegen die der Agent optimiert. Sie können nicht auseinanderdriften, weil es nur eine Kopie gibt.

Sechzig Sekunden

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

Das gebündelte Beispiel ist ein konfigurationsgesteuerter Checkout: vier Seiten, ein unzuverlässiges Zahlungs-Gateway, eine Wiederholungsrichtlinie, Kunden, die gehen. broken_checkout.json enthält die Fehler, die ein Agent tatsächlich macht, wenn er Konfiguration bearbeitet, die er nicht ausführen kann.

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

Sechs davon stammen aus einer Regeldatei. RETRY_BUDGET_TOO_THIN stammt aus acht Zeilen Python, denn „erfüllt dieses Wiederholungsbudget das Ausfallziel des Produkts" ist Arithmetik, kein Schema.

Nun sieh dir einen Lauf an:

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

Seed 3 erzeugt immer diese sieben Schritte – auf deinem Rechner, in CI, nächstes Jahr. Das macht es lesenswert.

Und zweitausend davon:

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

Der Teil, der sich bezahlt macht

Erhöhe eine Zahl – shipping.abandon_chance von 0.05 auf 0.28, die Art von Änderung, die wie ein Produkt-Feinschliff aussieht und das Review besteht:

$ 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

Strukturell perfekt. Einundzwanzig Prozentpunkte Conversion weg. Kein Schema, Typsystem oder Code-Review fängt das ab; ein Seeded Batch mit deklariertem Band fängt es in vier Sekunden, im Pull Request, bevor ein Mensch den Diff liest.

Es funktioniert auch in die andere Richtung. express_checkout erzielt eine höhere Erfolgsquote als der Standardfluss – 91,0 % – und ist die schlechtere Konfiguration: ihre Zahlungsausfälle liegen bei 3,9 % gegenüber 0,3 %, versteckt in einer Kennzahl, die gut aussieht. Das Aggregat übersieht es; der handgeschriebene Validator sagt es deutlich:

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

Keine Ebene ersetzt die andere. Deshalb gibt es zwei.

Einführung

Ein Modul, eine Konfigurationsdatei. examples/checkout-flow/groundtruth_app.py ist die gesamte Vorlage – etwa hundert Zeilen inklusive Kommentaren.

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)

Allein @kit.runner gibt dir sowohl replay als auch simulate – die Bibliothek führt es einmal pro Seed aus und behält das Ergebnis. Alles andere (Seed-Batching, Aggregation, Perzentile, Schwellenwert-Gating, Ausgabe-Budgetierung, Fehlerformulierung, die MCP-Oberfläche) kommt aus dem Paket.

# 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"

Dann sagt dir groundtruth doctor, was verdrahtet ist, groundtruth serve übergibt die Werkzeuge an einen Agenten, und groundtruth simulate --gate blockiert den Merge. Vollständige Anleitung mit domänenspezifischen Beispielen: docs/ADOPTION.md.

Regeln, die du kostenlos bekommst

Strukturprüfungen werden deklariert, nicht geschrieben. Zwölf Typen, die jeweils eine Art abdecken, wie strukturierte Konfigurationen tatsächlich verrotten:

Typ

Fängt

Schlüsselfelder

required_fields

halb geschriebene Einträge

select, fields

unique_key

doppelte IDs, die die Engine still überschattet

select, key

enum

einen Wert, den deine Engine nicht verarbeitet

select, values

type

einen String, wo eine Zahl hingehört

select, expect

range

1.4 in einem Feld, das eine Wahrscheinlichkeit ist

select, min, max

pattern

IDs, die einen Namensvertrag brechen

select, regex

not_empty

eine leere Liste, wo ein Eintrag erforderlich ist

select

ref_exists

einen Verweis auf etwas, das umbenannt wurde

select, collection, key

reachable

einen Knoten, den kein Pfad vom Start erreicht

collection, key, edges, start

no_dead_end

einen Nicht-Endknoten ohne Ausweg

collection, key, edges, terminal_field

no_self_loop

einen Knoten, der zu sich selbst übergeht

collection, key, edges

no_cycle

einen Ring ohne Ausgang (mit einer allow-Liste für die absichtlichen)

collection, key, edges

Selektoren sind eine bewusst kleine Pfadsprache – states[].transitions[].to – und jede Übereinstimmung meldet den konkreten Pfad, an dem sie gefunden wurde, was states[3].transitions[1].to möglich macht statt „ein Übergang ist ungültig".

Jede Regel akzeptiert optional code, severity und hint. Der Hint ist der Satz, nach dem ein Agent handelt; schreibe ihn also im Imperativ.

Schreibgeschützt bedeutet schreibgeschützt

query führt genau ein SELECT aus. Zwei Ebenen erzwingen das, und sie sind nicht gleichwertig.

Der Keyword-Scan ist User Experience: Er lehnt DELETE FROM … mit einem erklärenden Satz ab, statt mit einem Datenbankfehler, den das Modell entschlüsseln muss. Er ist nicht die Grenze – eine Blockliste über Text ist immer einen Fall von falsch entfernt, und die kanonische Demonstration ist SELECT * INTO audit_copy FROM users, das mit SELECT beginnt, kein verbotenes Verb enthält und eine Tabelle erzeugt.

Die Grenze ist der Datenspeicher: mode=ro plus PRAGMA query_only bei SQLite, eine READ ONLY-Transaktion bei PostgreSQL, ein Statement-Timeout bei beiden. Die Tests umgehen die Absicherung vollständig und bestätigen, dass die Verbindung sich weiterhin weigert.

Spalten-Redaktion ist die einzige Textebenen-Kontrolle, die Durchsetzung ist: Werte in deny_columns werden nach dem Abruf und bevor der Ergebnisstring existiert verworfen, sodass SELECT * sie nicht leaken kann. Alles Zurückgegebene ist in <untrusted>-Tags gewrappt, weil eine notes-Spalte, die etwas enthält, das wie eine Anweisung geformt ist, Daten sind und als Daten ankommen müssen.

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

Exit-Codes: 0 sauber, 1 Befunde (Lint-Fehler, ein Schwellenwert außerhalb seines Bandes, Nichtdeterminismus), 2 konnte nicht ausgeführt werden (schlechte Konfiguration, fehlende Fähigkeit, abgelehnte Abfrage). Füge --json zu lint, replay und simulate hinzu, für maschinenlesbare Ausgabe.

Installation

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+. Der Kern hat keine Drittanbieter-Abhängigkeiten – das ist beabsichtigt, damit das CI-Gate nicht vom Agent-Stack abhängt. Ein nackter Runner kann deine Schwellenwerte durchsetzen, ohne ein SDK zu installieren.

Verdrahtung verifizieren

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

Startet den Server als echten Subprozess, initialisiert über stdio, listet Werkzeuge, ruft zwei davon auf, gibt zurück, was zurückkam – dieselbe Sequenz, die ein Client ausführt. Führe es aus, bevor du dem Agenten die Schuld gibst, deine Werkzeuge nicht zu sehen.

Was CI bei jedem Pull Request durchsetzt

Kein Abzeichen, das bedeutet „die Tests liefen" – sechs Dinge, von denen jedes schon etwas blockiert hat:

Check

Warum es ein Gate ist und kein Vorschlag

ruff check + ruff format --check

Einschließlich BLE, also trägt jedes breite except eine schriftliche Begründung

mypy

Das Paket liefert py.typed; eine falsche Annotation ist eine falsche API

pytest auf 3.11 / 3.12 / 3.13

69 Tests, Abdeckungsuntergrenze 75 % (aktuell 78 % mit Branch-Abdeckung)

scripts/mcp_smoke.py

Ein echter Subprozess, echtes stdio, echtes tools/list und tools/call

simulate --gate --check-determinism

Das eigene Argument des Projekts, auf sich selbst angewendet

lint broken_checkout muss Exit 1 sein

Ein Lint, das nicht fehlschlagen kann, ist dekorativ

Einschränkungen, klar benannt

  • Die SQL-Tabellen-Allowlist ist textbasiert. Sie scannt nach Bezeichnern nach FROM und JOIN. Echte Durchsetzung pro Tabelle ist ein Datenbank-Grant; dies ist eine Schutzleiste mit einer guten Fehlermeldung, und die Read-only-Transaktion ist das, was tatsächlich hält.

  • Die Keyword-Blockliste greift innerhalb von String-Literalen. Eine Abfrage, die auf einen Wert mit grant filtert, wird abgelehnt. Das zu beheben erfordert einen echten SQL-Parser, den zu bauen sich nicht lohnt, wenn der Parser nicht die Grenze ist.

  • Auto-LIMIT ist eine Heuristik. Ein LIMIT in einer Unterabfrage unterdrückt das Hinzufügen auf oberster Ebene. max_rows begrenzt weiterhin, was gerendert wird.

  • Selektoren filtern nicht. states[].transitions[] geht durch alles; es gibt kein states[kind=terminal]. Eine Prädikatsprache wäre das dritte Feature, das niemand verlangt hat. Schreibe stattdessen einen @kit.validator.

  • Schwellenwerte sind projektweit, nicht pro Ziel. Jedes Ziel in einem Projekt wird gegen dieselben Bänder bewertet. Projekte, deren Konfigurationen wirklich unterschiedliche Bänder benötigen, sollten separate groundtruth.toml-Dateien sein.

  • Die PostgreSQL-Quelle ist implementiert, aber leicht getestet – die Testsuite beweist die Grenze gegen SQLite, wo sie überall ohne Service-Container laufen kann.

Woher das kommt

Extrahiert aus einer privaten Codebasis, in der sich das Muster seinen Platz verdient hat: eine Authoring-Pipeline, deren Mitwirkende immer wieder Konfigurationen auslieferten, die die Schema-Validierung bestanden und zur Laufzeit brachen. Die domänenspezifischen Teile blieben zurück. Was sich verallgemeinerte, war die Form — check, replay, simulate, query — plus eine Reihe von Entscheidungen, die sich als wichtiger herausstellten als die Feature-Liste:

  • Eine Schwellenwertliste, gelesen sowohl vom Agenten als auch von CI, weil zwei Kopien voneinander abwichen und das Tool eine Weile lang PASS für Zahlen meldete, die CI abgelehnt hätte.

  • Fehler, die die gültigen Alternativen inline benennen, denn ein Agent, der einen zweiten Aufruf tätigen muss, um zu erfahren, was er übergeben darf, wird stattdessen raten.

  • Tool-Beschreibungen, die aus der Live-Konfiguration zusammengestellt werden, denn eine veraltete Beschreibung ist ein Tool, das der Agent falsch und dennoch selbstbewusst verwendet.

  • Ausgabe auf jedem Pfad begrenzt, denn eine ausufernde Abfrage kann den Rest der Konversation verdrängen.

docs/ARCHITECTURE.md enthält die Modulübersicht und die vollständige Begründung.

Lizenz

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