Skip to main content
Glama

groundtruth-mcp

ci pypi python license

코딩 에이전트가 저장소의 모든 파일을 읽을 수 있어도 여전히 추측에 불과합니다. 이 프로젝트는 여러분의 프로젝트 자체의 검사, 재실행, 시뮬레이션, 쿼리를 MCP 도구로 바꿔서, 에이전트가 예측하는 대신 자신의 편집 결과를 관찰하도록 합니다.

중국어 문서 · 채택 가이드 · 아키텍처 · 고정 시드가 필요한 이유


문제

구조화된 구성(config) — 워크플로 그래프, 규칙 파일, 상태 머신, 파이프라인 정의 — 을 편집하는 에이전트는 잘못된 종류의 맥락으로 작업하고 있습니다. 스키마는 읽을 수 있습니다. 그러나 실제로 실행될 때 어떤 일이 일어나는지는 읽을 수 없습니다.

그래서 추론합니다. 재시도 한도를 바꾸고 안전하다고 말합니다. "안전하다"는 그럴듯해 보이는 diff가 주어졌을 때 가장 그럴듯한 다음 토큰이었기 때문입니다. 아무도 실행하지 않았습니다. 위반한 제약 조건은 세 파일 떨어진 불변식(invariant)에 있거나, 정책이 마지막으로 조정된 이후 아무도 샘플링하지 않은 분포에 있습니다.

해결책은 더 나은 프롬프트가 아닙니다. 에이전트에게 관찰할 무언가를 주는 것입니다.

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

여러분이 직접 작성하는 네 개의 작은 함수로 만들어진 다섯 가지 도구:

도구

답변

유용하게 만드는 속성

lint

이 구성은 자체적으로 일관성이 있는가?

모든 문제는 편집할 정확한 경로를 포함한다

replay

내가 이것을 실행하면 어떻게 되나?

(config, seed)의 순수 함수 — 어디서나 재현 가능

simulate

내 변경이 전반적으로 더 나은가, 더 나쁜가?

시드 배치, 분포, 임계값, 통과/실패

query

데이터에 실제로 무엇이 있는가?

정규식이 아닌 데이터베이스가 강제하는 읽기 전용

describe_data

어떤 테이블이 존재하는가?

아무도 스키마를 추측할 필요가 없도록

동일한 기능이 CLI로도 실행됩니다. 따라서 groundtruth simulate --gate는 에이전트가 최적화 대상으로 삼는 동일한 임계값을 읽는 병합 게이트입니다. 복사본이 하나뿐이므로 어긋날 수 없습니다.

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

번들로 제공되는 예제는 구성 기반 체크아웃입니다. 네 개의 페이지, 불안정한 결제 게이트웨이, 재시도 정책, 이탈하는 고객들이 있습니다. 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에서 나옵니다. "이 재시도 예산이 제품의 실패 목표를 충족하는가"는 스키마가 아니라 산술이기 때문입니다.

이제 실행 하나를 지켜보세요:

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_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포인트가 사라졌습니다. 어떤 스키마, 타입 시스템, 코드 리뷰도 그걸 잡아내지 못합니다. 선언된 밴드가 있는 시드 배치는 사람이 diff를 읽기 전에 풀 리퀘스트에서 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%)

어느 한 계층도 다른 계층을 포함하지 않습니다. 그래서 두 개인 것입니다.

도입하기

모듈 하나, 구성 파일 하나. 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만으로 replay와 simulate를 모두 얻을 수 있습니다. 라이브러리가 시드마다 한 번씩 실행하고 결과를 보관합니다. 그 외의 모든 것(시드 배칭, 집계, 백분위수, 임계값 게이팅, 출력 예산, 오류 표현, 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

나갈 방법이 없는 비단말(non-terminal) 노드

collection, key, edges, terminal_field

no_self_loop

자기 자신으로 전이하는 노드

collection, key, edges

no_cycle

출구가 없는 순환 (의도적인 순환을 위한 allow 목록 포함)

collection, key, edges

선택자(selector)는 의도적으로 작게 만든 경로 언어입니다 — states[].transitions[].to — 그리고 모든 일치 항목은 발견된 구체적인 경로를 보고하므로, "전환이 유효하지 않습니다" 대신 states[3].transitions[1].to를 말할 수 있습니다.

각 규칙은 선택적으로 code, severity, hint를 받습니다. 힌트는 에이전트가 행동하는 문장이므로 명령형으로 작성하세요.

읽기 전용은 읽기 전용이다

query는 하나의 SELECT를 실행합니다. 두 계층이 이를 강제하며, 둘은 동등하지 않습니다.

키워드 스캔은 사용자 경험을 위한 것입니다. DELETE FROM …을 데이터베이스 오류 대신 "그렇게 할 수 없다"는 문장과 함께 거부합니다. 모델이 해독해야 하는 데이터베이스 오류 대신입니다. 이는 경계가 아닙니다. 텍스트에 대한 블록리스트는 항상 하나의 사례만으로도 틀릴 수 있으며, 전형적인 예는 SELECT * INTO audit_copy FROM users입니다. 이는 SELECT로 시작하고 거부된 동사가 없으며 테이블을 생성합니다.

경계는 데이터 저장소입니다. SQLite에서는 mode=ro와 PRAGMA query_only, PostgreSQL에서는 READ ONLY 트랜잭션, 둘 다에는 문장 타임아웃이 적용됩니다. 테스트는 가드를 완전히 우회하여 연결이 여전히 거부하는지 확인합니다.

열(column) 편집은 실제로 강제하는 유일한 텍스트 수준 제어입니다. 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 발견 사항 있음(린트 오류, 밴드를 벗어난 임계값, 비결정성), 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+. 핵심에는 제3자 의존성이 없습니다 — 의도적인 설계로, 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 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 must exit 1

실패할 수 없는 린트는 장식일 뿐이다

한계, 명확히 밝히면

  • SQL 테이블 허용 목록은 텍스트 기반입니다. FROM과 JOIN 뒤의 식별자를 검사합니다. 실제 테이블별 강제는 데이터베이스 권한이며, 이것은 좋은 오류 메시지를 제공하는 가드레일일 뿐입니다. 실제로 지탱하는 것은 읽기 전용 트랜잭션입니다.

  • 키워드 블록리스트는 문자열 리터럴 내부에서도 일치합니다. grant를 포함한 값으로 필터링하는 쿼리는 거부됩니다. 이를 고치려면 실제 SQL 파서가 필요한데, 파서가 경계가 아닌 상황에서 파서를 만드는 것은 가치가 없습니다.

  • 자동 LIMIT은 휴리스틱입니다. 하위 쿼리 내부의 LIMIT은 최상위 추가를 억제합니다. max_rows는 여전히 렌더링되는 결과의 상한을 정합니다.

  • 선택자는 필터링하지 않습니다. states[].transitions[]는 모든 것을 탐색합니다. states[kind=terminal] 같은 것은 없습니다. 술어 언어는 아무도 요구하지 않은 세 번째 기능이 될 것입니다. 대신 @kit.validator를 작성하세요.

  • 임계값은 프로젝트 전체에 적용되며 대상별이 아닙니다. 프로젝트의 모든 대상은 동일한 밴드로 판정됩니다. 구성에 실제로 다른 밴드가 필요한 프로젝트는 별도의 groundtruth.toml 파일을 사용해야 합니다.

  • PostgreSQL 소스는 구현되어 있지만 가볍게만 테스트되었습니다. 테스트 스위트는 서비스 컨테이너 없이 어디서나 실행할 수 있는 SQLite에서 경계를 검증합니다.

이 프로젝트는 어디서 왔는가

이 패턴이 자리 잡은 프라이빗 코드베이스에서 추출했습니다: 기여자들이 스키마 검증을 통과했지만 런타임에서 깨지는 구성을 계속 배포하던 작성 파이프라인입니다. 도메인별 부분은 뒤에 남았습니다. 일반화된 것은 형태 — 체크, 리플레이, 시뮬레이션, 쿼리 — 그리고 기능 목록보다 더 중요했던 일련의 결정들이었습니다:

  • 에이전트와 CI가 함께 읽는 임계값 목록은 하나뿐입니다. 사본이 두 개가 되면 어긋나서 도구가 CI가 거부했을 수치에 대해 PASS를 보고하는 일이 발생했기 때문입니다.

  • 유효한 대안을 인라인으로 명시하는 오류. 무엇을 전달할 수 있는지 알기 위해 두 번째 호출을 해야 하는 에이전트는 추측할 것이기 때문입니다.

  • 실제 구성에서 생성된 도구 설명. 오래된 설명은 에이전트가 확신을 가지고 잘못 사용하는 도구가 되기 때문입니다.

  • 모든 경로에서 출력 제한. 한 번의 과도한 쿼리가 대화의 나머지를 밀어낼 수 있기 때문입니다.

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