Recce MCP 서버 개발
아키텍처
RecceMCPServer는 MCP SDK Server를 통해 list_tools/call_tool 핸들러를 등록합니다. call_tool은 _tool_* 메서드로 디스패치하고, 오류를 분류한 후 로깅/메트릭 전송 후 재발생시킵니다.
진입점 run_mcp_server()는 single_env를 pop한 후 나머지 kwargs를 load_context()에 전달합니다.
주요 패턴
오류 분류 — 공유된 지표 리스트는 recce/tasks/rowcount.py에 정의됩니다. 우선 순위(PERMISSION_DENIED > TABLE_NOT_FOUND > SYNTAX_ERROR)는 mcp_server.py의 _classify_db_error()와 rowcount.py의 _query_row_count()에서 적용됩니다. 분류된 경우 → logger.warning() + sentry_metrics.count() (sentry_sdk가 있을 때). 분류되지 않은 경우 → logger.error() + traceback.
MCP SDK 특성 — 핸들러는 SDK가 isError=True를 설정하도록 raise해야 합니다.
응답 계약 — CLAUDE.md 참조. 추가적인 _meta만 허용됩니다. summary.py: dict.get(key, 0) 대신 is None으로 검사합니다. N/A 표시에는 이유를 포함합니다: "N/A (table_not_found)".
단일 환경 — _maybe_add_single_env_warning()은 diff 결과에 _warning을 추가합니다. 설명에 조건부 노트가 붙습니다.
테스트 (세 가지 계층)
| 계층 | 파일 | 데이터 소스 | 실행 환경 | 목적 |
|---|---|---|---|---|
| 단위 | tests/test_mcp_server.py | Mock RecceContext | CI (pytest) | 로직 정확성 — 툴 핸들러, 오류 분류, 응답 형식 |
| 통합 | tests/test_mcp_e2e.py | DbtTestHelper + DuckDB (고정 데이터) | CI (pytest) | MCP 프로토콜이 anyio 메모리 스트림을 통해 종단 간 동작 |
| 수동 (E2E) | /recce-mcp-e2e 스킬 | 사용자의 실제 dbt 프로젝트 + 실제 데이터베이스 | 수동 | 8개 모든 도구가 실제 데이터에 대해 유효한 결과 반환 |
새로운 MCP 기능이나 동작 변경은 세 가지 계층에서 모두 테스트되어야 합니다.
테스트 커버리지 갭 분석
MCP 변경 작업(아래 E2E 게이트 참조)이 완료된 후, E2E 검증을 요청하기 전에 세 가지 계층에서 누락된 테스트 커버리지를 적극적으로 스캔하십시오.
확인 방법:
- 변경된 사항 식별 — 새로운 툴 핸들러? 새로운 오류 경로? 새로운 응답 필드?
- 각 변경 사항에 대해 각 계층에서 커버리지가 있는지 확인:
- 단위:
tests/test_mcp_server.py에 새로운 동작에 대한 테스트 케이스가 있습니까? (정상 경로 + 오류 경로) - 통합:
tests/test_mcp_e2e.py가 MCP 프로토콜을 통해 새로운 도구/기능을 실행합니까? - 수동:
/recce-mcp-e2e템플릿이 새로운 도구를 포함합니까? (새 도구가 추가된 경우 템플릿 업데이트가 필요할 수 있음)
- 단위:
갭이 발견되면, E2E 게이트 프롬프트 전에 사용자에게 보고:
테스트 커버리지 갭 발견:
- 단위:
_tool_foo오류 경로(table_not_found)에 대한 테스트 누락- 통합:
test_mcp_e2e.py가foo도구를 실행하지 않음- 수동:
/recce-mcp-e2e템플릿에foo도구가 포함되지 않음E2E 실행 전에 이 갭을 채우시겠습니까?
스캔하지 않음: 테스트 전용 변경, 주석/문서 편집, import 재정렬.
E2E 검증 게이트
MCP 변경의 각 의미 있는 라운드 후에, 반드시 사용자에게 질문:
이번 라운드 MCP 변경이 완료되었습니다.
/recce-mcp-e2e를 실행하여 확인하시겠습니까?
사용자가 예라고 하면 /recce-mcp-e2e를 호출하십시오. 이전 세션에서 dbt 프로젝트 경로가 사용된 경우 자동으로 재사용하고, 그렇지 않으면 물어보십시오.
"라운드"로 간주되는 경우:
- 툴 핸들러 추가 또는 수정 + 해당 단위 테스트 통과
- 오류 분류 로직 변경 + 테스트 통과
- 단일 환경 또는 응답 형식 변경 + 테스트 통과
질문하지 않음: 테스트 전용 변경, 주석/문서 편집, import 재정렬.
이는 tests/test_mcp_e2e.py와 별개입니다 — 해당 파일은 CI에서 DbtTestHelper + DuckDB로 테스트합니다. /recce-mcp-e2e는 실제 데이터베이스가 있는 실제 dbt 프로젝트에 대해 8개 모든 도구를 검증합니다.
주의사항
sentry_sdkimport:except블록에# pragma: no cover(CI에는 항상 있음)- Python 3.9:
Union[X, Y](notX | Y) - Pre-commit: black/isort가 재포맷할 수 있음 — 다시 스테이징하고 커밋
run.py의schema_diff_should_be_approved()try/except는 의도적입니다 (체크 생성 보장)
파일 맵
recce/mcp_server.py (서버 + 핸들러), recce/tasks/rowcount.py (오류 지표, RowCountStatus), recce/run.py (CLI 프리셋), recce/summary.py (표시 로직), recce/event/__init__.py (Sentry)