Skip to content

동작 원리 ​

이 문서를 끝내면 mcpeak test 한 번에 움직이는 부품이 무엇이고, 각 부품이 결정론성을 어떻게 지키는지 이해한 상태가 된다. 명령어 자체는 빠른 시작과 CLI 레퍼런스에서 다룬다.

한 번의 실행 ​

명세(JSON) ──→ runner ──→ core ──→ 서버 프로세스
                 │          │
                 │          └── stdio 또는 Streamable HTTP
                 └── 케이스 순서대로 실행, 단언 판정, 보고서
  1. CLI 가 명세 파일을 읽고 검증한다. 필드가 빠졌거나 작업과 맞지 않는 단언이 있으면 서버를 띄우기 전에 멈춘다.
  2. core 가 서버에 붙는다. -- 뒤의 명령이면 프로세스를 stdio 로 띄우고, --url 이면 이미 떠 있는 Streamable HTTP 서버에 붙는다. handshake 가 끝나면 listTools 와 callTool 만 가진 클라이언트를 runner 에 넘긴다.
  3. runner 가 케이스를 명세 순서대로 실행한다. 케이스마다 작업 하나를 보내고 단언을 판정한다. 타임아웃이나 중단이 나면 이후 케이스는 notRun 으로 남긴다.
  4. runner 가 보고서를 만들고 CLI 가 그것을 사람용 텍스트, --json, --junit 으로 찍는다.
  5. core 가 서버를 닫는다. pending 요청을 잠시 기다린 뒤 정상 종료를 시도하고, 그래도 안 끝나면 강제 종료한다.

패키지 ​

패키지역할
@mcpeak/coreMCP 프로토콜 클라이언트, 트랜스포트, 프로세스 수명주기
@mcpeak/runner명세 실행, 단언, 실패 메시지, 보고서와 JUnit
@mcpeak/generate스키마에서 결정론적 baseline 생성, 승인형 AI 검토
@mcpeak/record서버가 밖으로 부르는 HTTP 의 녹화·재생
@mcpeak/mock목 MCP(stdio · Streamable HTTP)
@mcpeak/audit서버를 쓰기 전의 보안 점검, Docker 격리 안의 행위 관측
@mcpeak/optimize도구 정의의 무손실 압축, 오버레이를 서빙하는 프록시
@mcpeak/cli진입점. 얇게 유지한다
@mcpeak/dashboard로컬 웹 UI. CLI 의 커맨드 함수를 재사용한다

의존은 한 방향이다. dashboard → cli → runner / generate / record / mock / audit / optimize → core. 오른쪽 하위 계층으로만 의존할 수 있고 역참조는 없다.

서버 프로세스는 격리된다 ​

core 는 부모의 process.env 를 자식에게 통째로 넘기지 않는다. 부모 환경에는 인증 토큰이 섞일 수 있고, 실행마다 달라지는 값이 결과를 흔들 수 있어서다. SDK 가 허용한 고정 목록만 넘기고, 서버가 더 필요로 하는 값은 --env NAME 으로 이름을 지정한 것만 간다.

실행은 셸을 거치지 않는다. args 에 공백이나 셸 문자가 있어도 다시 해석되지 않는다.

stderr 는 신뢰할 수 없는 입력으로 다룬다. 최근 64 KiB 만 남기고, 실패했을 때 진단 블록에 마지막 몇 줄을 보여 준다. 기본 오류 메시지에는 넣지 않는다.

실패 메시지가 만들어지는 자리 ​

단언이 실패하면 runner 가 세 줄을 만든다. 무엇이 달랐는지, 서버가 실제로 무엇을 돌려줬는지, 어떻게 고칠지다.

isError  오류 응답을 기대했지만 정상 응답을 받았습니다.
→ {"sum":null}
해결: 툴 입력값과 서버의 오류 응답을 확인하세요.

응답 본문은 그대로 싣되, authorization · token · password 같은 이름의 값은 가린다. 케이스당 64 KiB, 보고서당 1 MiB 를 넘는 부분은 잘라 낸다.

결정론성을 지키는 방법 ​

같은 입력에 항상 같은 결과가 나와야 한다. 도구의 각 부분이 그것을 위해 하는 일이다.

  • generate 는 같은 툴 정의와 옵션에서 같은 명세를 낸다. 입력값은 스키마에서 정해진 순서로 고르고 랜덤값을 쓰지 않는다.
  • 승인 지문은 파일 바이트가 아니라 파싱된 명세 객체로 계산한다. 들여쓰기와 키 순서를 바꿔도 같다.
  • 목의 매칭 키는 객체 키 순서에 영향받지 않는다. HTTP 목은 stateless 로 떠서 SDK 가 세션 ID 를 랜덤으로 만들지 않게 한다.
  • External 세션은 서버가 밖으로 부른 응답을 저장해 두고 재생한다. 호출마다 바뀌는 기온이나 환율이 고정된다.
  • JUnit 의 time 은 항상 0 이다. 보고서에 시간 정보를 두지 않는다.
  • --determinism 은 같은 명세를 두 번 돌려 차이를 드러낸다. 도구가 비결정성을 지워서 감추지 않고, 있으면 알려 준다.

승인 지문 ​

generate 로 만든 명세에는 승인 시점의 요약이 붙는다. test 는 매번 그 값을 다시 계산해 파일의 값과 대조한다. 판정은 바꾸지 않고 보고서에만 적는다. 명세를 고치는 것은 정상 작업이고, 그때마다 테스트가 막히면 사용자는 확인 절차를 우회하는 방법부터 찾게 된다. 자세한 규칙은 명세 생성에 있다.

설계 결정 ​

"다르게 갈 수도 있었던" 판단은 저장소의 docs/adr/ 에 한 페이지씩 남아 있다. 무엇이 살아 있는 API 인지는 ADR 과 소스의 @deprecated 주석이 기준이다.

다음에 읽을 것 ​