groundtruth-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@groundtruth-mcprun lint on my edited checkout config"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
groundtruth-mcp
Your coding agent can read every file in your repo and still be guessing. This turns your project's own checks, replays, simulations and queries into MCP tools, so it observes the consequences of its edit instead of predicting them.
中文文档 · Adoption guide · Architecture · Why fixed seeds
The problem
An agent editing a structured config — a workflow graph, a rules file, a state machine, a pipeline definition — is working with the wrong kind of context. It can read the schema. It cannot read what happens when the thing runs.
So it infers. It changes a retry limit and tells you the change is safe, because "safe" was the most plausible next token given a diff that looked reasonable. Nobody ran anything. The constraint it violated lives in an invariant three files away, or in a distribution nobody has sampled since the policy was last tuned.
The fix is not a better prompt. It is giving the agent something to observe.
What it does
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| SFive tools, built from four small functions you write:
Tool | Answers | Property that makes it useful |
| Is this config self-consistent? | Every issue carries the exact path to edit |
| What happens when I run this one? | Pure function of |
| Is my change better or worse overall? | Seeded batch, distribution, thresholds, pass/fail |
| What is actually in the data? | Read-only enforced by the database, not by a regex |
| What tables exist? | So nothing has to guess a schema |
The same capabilities run as a CLI, so groundtruth simulate --gate is a merge
gate reading the same thresholds the agent optimises against. They cannot
drift, because there is only one copy.
Sixty seconds
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_checkoutThe bundled example is a config-driven checkout: four pages, a flaky payment
gateway, a retry policy, customers who leave. broken_checkout.json contains
the mistakes an agent actually makes when editing config it cannot run.
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 inSix of those come from a rule file. RETRY_BUDGET_TOO_THIN comes from eight
lines of Python, because "does this retry budget meet the product's failure
target" is arithmetic, not a schema.
Now watch one run:
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: successSeed 3 always produces those seven steps — on your machine, in CI, next year. That is what makes it worth reading.
And two thousand of them:
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 identicallyThe part that earns its keep
Raise one number — shipping.abandon_chance from 0.05 to 0.28, the kind of
edit that looks like a product tweak and passes review:
$ 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 1Structurally perfect. Twenty-one points of conversion gone. No schema, type system or code review catches that; a seeded batch with a declared band catches it in four seconds, on the pull request, before a human reads the diff.
It works in the other direction too. express_checkout posts a higher success
rate than the standard flow — 91.0% — and is the worse config: its payment
failures are 3.9% against 0.3%, hidden inside a headline number that looks
fine. The aggregate misses it; the hand-written validator says so plainly:
[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%)Neither layer subsumes the other. That is why there are two.
Adopting it
One module, one config file. examples/checkout-flow/groundtruth_app.py is the
whole template — about a hundred lines including comments.
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 alone gives you both replay and simulate — the library runs it
once per seed and keeps the outcome. Everything else (seed batching,
aggregation, percentiles, threshold gating, output budgeting, error phrasing,
the MCP surface) comes from the package.
# 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"Then groundtruth doctor tells you what is wired up, groundtruth serve hands
the tools to an agent, and groundtruth simulate --gate blocks the merge. Full
walkthrough with per-domain examples: docs/ADOPTION.md.
Rules you get for free
Structural checks are declared, not written. Twelve types, each covering a way structured configs actually rot:
Type | Catches | Key fields |
| half-written entries |
|
| duplicate ids the engine silently shadows |
|
| a value your engine does not handle |
|
| a string where a number belongs |
|
|
|
|
| ids that break a naming contract |
|
| an empty list where one entry is required |
|
| a reference to something that was renamed |
|
| a node no path from start reaches |
|
| a non-terminal node with no way out |
|
| a node that transitions to itself |
|
| a ring with no exit (with an |
|
Selectors are a deliberately small path language — states[].transitions[].to
— and every match reports the concrete path it was found at, which is what
makes states[3].transitions[1].to possible instead of "a transition is
invalid".
Each rule takes an optional code, severity and hint. The hint is the
sentence an agent acts on, so write it in the imperative.
Read-only means read-only
query runs one SELECT. Two layers enforce that, and they are not equals.
The keyword scan is user experience: it rejects DELETE FROM … with a sentence
saying so, instead of a database error the model has to decode. It is not
the boundary — a blocklist over text is always one case away from wrong, and
the canonical demonstration is SELECT * INTO audit_copy FROM users, which
starts with SELECT, contains no denied verb, and creates a table.
The boundary is the datastore: mode=ro plus PRAGMA query_only on SQLite, a
READ ONLY transaction on PostgreSQL, a statement timeout on both. The tests
go around the guard entirely and confirm the connection still refuses.
Column redaction is the one text-level control that is enforcement: values in
deny_columns are dropped after the fetch and before the result string exists,
so SELECT * cannot leak them. Everything returned is wrapped in <untrusted>
tags, because a notes column containing something shaped like an instruction
is data and has to arrive labelled as data.
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 stdioExit codes: 0 clean, 1 findings (lint errors, a threshold outside its band,
non-determinism), 2 could not run (bad config, missing capability, rejected
query). Add --json to lint, replay and simulate for machine-readable
output.
Install
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+. The core has no third-party dependencies — that is deliberate, so the CI gate does not depend on the agent stack. A bare runner can enforce your thresholds without installing an SDK.
Verifying a wiring
python scripts/mcp_smoke.py [path/to/groundtruth.toml]Spawns the server as a real subprocess, initializes over stdio, lists tools, calls two of them, prints what came back — the same sequence a client performs. Run it before blaming the agent for not seeing your tools.
What CI enforces on every pull request
Not a badge that means "the tests ran" — six things, each of which has blocked something:
Check | Why it is a gate and not a suggestion |
| Including |
| The package ships |
| 69 tests, coverage floor 75% (currently 78% with branch coverage) |
| A real subprocess, real stdio, real |
| The project's own argument, applied to itself |
| A lint that cannot fail is decorative |
Limitations, stated plainly
The SQL table allowlist is textual. It scans for identifiers after
FROMandJOIN. Real per-table enforcement is a database grant; this is a guardrail with a good error message, and the read-only transaction is what actually holds.The keyword blocklist matches inside string literals. A query filtering on a value containing
grantgets rejected. Fixing that needs a real SQL parser, which is not worth building when the parser is not the boundary.Auto-
LIMITis a heuristic. ALIMITinside a subquery suppresses the top-level append.max_rowsstill caps what is rendered.Selectors do not filter.
states[].transitions[]walks everything; there is nostates[kind=terminal]. A predicate language would be the third feature nobody asked for. Write a@kit.validatorinstead.Thresholds are project-wide, not per-target. Every target in a project is judged against the same bands. Projects whose configs need genuinely different bands should be separate
groundtruth.tomlfiles.The PostgreSQL source is implemented but lightly exercised — the test suite proves the boundary against SQLite, where it can run everywhere without a service container.
Where this came from
Extracted from a private codebase where the pattern earned its place: an authoring pipeline whose contributors kept shipping configs that passed schema validation and broke at runtime. The domain-specific parts stayed behind. What generalised was the shape — check, replay, simulate, query — plus a set of decisions that turned out to matter more than the feature list:
One threshold list, read by both the agent and CI, because two copies drifted and the tool spent a while reporting PASS on numbers CI would have rejected.
Errors that name the valid alternatives inline, because an agent that has to make a second call to learn what it may pass will instead guess.
Tool descriptions composed from live config, because a stale description is a tool the agent uses wrongly and confidently.
Output capped on every path, because one enthusiastic query can evict the rest of the conversation.
docs/ARCHITECTURE.md has the module map and the full reasoning.
License
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