Skip to main content
Glama

groundtruth-mcp

ci pypi python license

Tu agente de codificación puede leer todos los archivos de tu repositorio y aun así estar adivinando. Esto convierte las comprobaciones, reproducciones, simulaciones y consultas propias de tu proyecto en herramientas MCP, de modo que observa las consecuencias de su edición en lugar de predecirlas.

Documentación en chino · Guía de adopción · Arquitectura · Por qué usar semillas fijas


El problema

Un agente que edita una configuración estructurada — un grafo de flujo de trabajo, un archivo de reglas, una máquina de estados, una definición de pipeline — está trabajando con el tipo de contexto equivocado. Puede leer el esquema. No puede leer qué ocurre cuando la cosa se ejecuta.

Por lo tanto, infiere. Cambia un límite de reintentos y te dice que el cambio es seguro, porque «seguro» era el siguiente token más plausible dado un diff que parecía razonable. Nadie ejecutó nada. La restricción que violó vive en un invariante a tres archivos de distancia, o en una distribución que nadie ha muestreado desde que la política se ajustó por última vez.

La solución no es un mejor prompt. Es darle al agente algo que observar.

Qué hace

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

Cinco herramientas, construidas a partir de cuatro pequeñas funciones que tú escribes:

Herramienta

Responde

Propiedad que la hace útil

lint

¿Es esta configuración autoconsistente?

Cada incidencia lleva la ruta exacta a editar

replay

¿Qué ocurre cuando ejecuto esta?

Función pura de (config, seed) — reproducible en cualquier lugar

simulate

¿Mi cambio es mejor o peor en general?

Lote con semilla, distribución, umbrales, pasa/falla

query

¿Qué hay realmente en los datos?

Solo lectura impuesta por la base de datos, no por una expresión regular

describe_data

¿Qué tablas existen?

Para que nada tenga que adivinar un esquema

Las mismas capacidades se ejecutan como CLI, por lo que groundtruth simulate --gate es una puerta de merge que lee los mismos umbrales contra los que optimiza el agente. No pueden divergir, porque solo hay una copia.

Sesenta segundos

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

El ejemplo incluido es un proceso de compra dirigido por configuración: cuatro páginas, una pasarela de pago inestable, una política de reintentos, clientes que se van. broken_checkout.json contiene los errores que un agente comete realmente cuando edita configuración que no puede ejecutar.

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

Seis de esos errores provienen de un archivo de reglas. RETRY_BUDGET_TOO_THIN proviene de ocho líneas de Python, porque «¿cumple este presupuesto de reintentos el objetivo de fallos del producto?» es aritmética, no un esquema.

Ahora observa una ejecución:

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

La semilla 3 siempre produce esos siete pasos —en tu máquina, en CI, el año que viene—. Eso es lo que hace que valga la pena leerlo.

Y dos mil de ellos:

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

La parte que justifica su existencia

Sube un número —shipping.abandon_chance de 0.05 a 0.28, el tipo de edición que parece un ajuste de producto y pasa la revisión:

$ 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

Estructuralmente perfecto. Veintiún puntos de conversión perdidos. Ningún esquema, sistema de tipos ni revisión de código detecta eso; un lote con semilla y una banda declarada lo detecta en cuatro segundos, en el pull request, antes de que un humano lea el diff.

También funciona en la otra dirección. express_checkout presenta una tasa de éxito más alta que el flujo estándar —91,0%— y es la peor configuración: sus fallos de pago son del 3,9% frente al 0,3%, ocultos dentro de una cifra destacada que parece correcta. El agregado no lo ve; el validador escrito a mano lo dice con claridad:

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

Ninguna capa subsume a la otra. Por eso hay dos.

Cómo adoptarlo

Un módulo, un archivo de configuración. examples/checkout-flow/groundtruth_app.py es la plantilla completa —unas cien líneas, comentarios incluidos—.

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 por sí solo te da tanto replay como simulate: la librería lo ejecuta una vez por semilla y conserva el resultado. Todo lo demás (agrupación de semillas, agregación, percentiles, control de umbrales, presupuesto de salida, redacción de errores, la superficie MCP) viene del paquete.

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

Luego groundtruth doctor te dice qué está conectado, groundtruth serve entrega las herramientas a un agente, y groundtruth simulate --gate bloquea el merge. Guía completa con ejemplos por dominio: docs/ADOPTION.md.

Reglas que obtienes gratis

Las comprobaciones estructurales se declaran, no se escriben. Doce tipos, cada uno cubre una forma en la que las configuraciones estructuradas realmente se degradan:

Tipo

Detecta

Campos clave

required_fields

entradas a medio escribir

select, fields

unique_key

ids duplicados que el motor silencia

select, key

enum

un valor que tu motor no maneja

select, values

type

una cadena donde corresponde un número

select, expect

range

1.4 en un campo que es una probabilidad

select, min, max

pattern

ids que rompen un contrato de nomenclatura

select, regex

not_empty

una lista vacía donde se requiere una entrada

select

ref_exists

una referencia a algo que fue renombrado

select, collection, key

reachable

un nodo al que ningún camino desde el inicio alcanza

collection, key, edges, start

no_dead_end

un nodo no terminal sin salida

collection, key, edges, terminal_field

no_self_loop

un nodo que transiciona a sí mismo

collection, key, edges

no_cycle

un anillo sin salida (con una lista allow para los intencionales)

collection, key, edges

Los selectores son un lenguaje de rutas deliberadamente pequeño —states[].transitions[].to— y cada coincidencia informa de la ruta concreta en la que se encontró, que es lo que hace posible states[3].transitions[1].to en lugar de «una transición no es válida».

Cada regla acepta un code, una severity y un hint opcionales. El hint es la frase sobre la que actúa un agente, así que escríbela en imperativo.

Solo lectura significa solo lectura

query ejecuta un único SELECT. Dos capas lo garantizan, y no son equivalentes.

El escaneo de palabras clave es experiencia de usuario: rechaza DELETE FROM … con una frase que lo dice, en lugar de un error de base de datos que el modelo tiene que descifrar. no es el límite —una lista negra sobre texto siempre está a un caso de ser incorrecta, y la demostración canónica es SELECT * INTO audit_copy FROM users, que empieza con SELECT, no contiene ningún verbo denegado y crea una tabla.

El límite es el almacén de datos: mode=ro más PRAGMA query_only en SQLite, una transacción READ ONLY en PostgreSQL, y un tiempo de espera de sentencia en ambos. Las pruebas sortean por completo la protección y confirman que la conexión sigue rechazando.

La redacción de columnas es el único control a nivel de texto que es una medida de cumplimiento: los valores en deny_columns se eliminan después de la obtención y antes de que exista la cadena de resultado, por lo que SELECT * no puede filtrarlos. Todo lo devuelto se envuelve en etiquetas <untrusted>, porque una columna notes que contiene algo con forma de instrucción es un dato y debe llegar etiquetado como dato.

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

Códigos de salida: 0 limpio, 1 hallazgos (errores de lint, un umbral fuera de su banda, no determinismo), 2 no se pudo ejecutar (mala configuración, capacidad faltante, consulta rechazada). Añade --json a lint, replay y simulate para una salida legible por máquina.

Instalación

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+. El núcleo no tiene dependencias de terceros —es intencionado—, para que la puerta de CI no dependa del stack del agente. Un ejecutor básico puede hacer cumplir tus umbrales sin instalar un SDK.

Verificar una integración

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

Lanza el servidor como un subproceso real, inicializa a través de stdio, lista las herramientas, llama a dos de ellas e imprime lo que devuelve —la misma secuencia que realiza un cliente—. Ejecútalo antes de culpar al agente por no ver tus herramientas.

Lo que CI exige en cada pull request

No es una insignia que signifique «las pruebas se ejecutaron» —seis cosas, cada una de las cuales ha bloqueado algo:

Verificación

Por qué es una puerta y no una sugerencia

ruff check + ruff format --check

Incluye BLE, por lo que cada except amplio lleva una justificación escrita

mypy

El paquete incluye py.typed; una anotación incorrecta es una API incorrecta

pytest on 3.11 / 3.12 / 3.13

69 pruebas, cobertura mínima del 75% (actualmente 78% con cobertura de ramas)

scripts/mcp_smoke.py

Un subproceso real, stdio real, tools/list y tools/call reales

simulate --gate --check-determinism

El propio argumento del proyecto, aplicado a sí mismo

lint broken_checkout debe terminar con 1

Un lint que no puede fallar es decorativo

Limitaciones, expresadas con claridad

  • La lista blanca de tablas SQL es textual. Escanea identificadores después de FROM y JOIN. La aplicación real por tabla es un permiso de base de datos; esto es una salvaguarda con un buen mensaje de error, y la transacción de solo lectura es lo que realmente aguanta.

  • La lista negra de palabras clave coincide dentro de literales de cadena. Una consulta que filtra por un valor que contiene grant es rechazada. Arreglar eso requiere un parser SQL real, que no merece la pena construir cuando el parser no es el límite.

  • El LIMIT automático es una heurística. Un LIMIT dentro de una subconsulta suprime la adición de nivel superior. max_rows sigue limitando lo que se muestra.

  • Los selectores no filtran. states[].transitions[] recorre todo; no existe states[kind=terminal]. Un lenguaje de predicados sería la tercera característica que nadie pidió. Escribe un @kit.validator en su lugar.

  • Los umbrales son para todo el proyecto, no por destino. Cada destino de un proyecto se evalúa con las mismas bandas. Los proyectos cuyas configuraciones necesitan bandas realmente distintas deberían ser archivos groundtruth.toml separados.

  • El origen de datos de PostgreSQL está implementado pero poco ejercitado —el conjunto de pruebas demuestra el límite contra SQLite, donde puede ejecutarse en cualquier lugar sin un contenedor de servicio.

De dónde viene esto

Extraído de una base de código privada donde el patrón se ganó su lugar: un pipeline de autoría cuyos colaboradores seguían enviando configuraciones que pasaban la validación de esquema y fallaban en tiempo de ejecución. Las partes específicas del dominio quedaron atrás. Lo que se generalizó fue la forma — check, replay, simulate, query — más un conjunto de decisiones que resultaron importar más que la lista de características:

  • Una lista de umbrales, leída tanto por el agente como por CI, porque dos copias se desincronizaron y la herramienta estuvo un tiempo reportando PASS sobre números que CI habría rechazado.

  • Errores que nombran las alternativas válidas en línea, porque un agente que tiene que hacer una segunda llamada para aprender qué puede pasar, en su lugar adivinará.

  • Descripciones de herramientas compuestas a partir de la configuración en vivo, porque una descripción obsoleta es una herramienta que el agente usa de manera incorrecta y con confianza.

  • Salida limitada en cada ruta, porque una consulta entusiasta puede desalojar el resto de la conversación.

docs/ARCHITECTURE.md contiene el mapa de módulos y el razonamiento completo.

Licencia

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