
Claude Code의 하네스는 밑바닥부터 쌓아야 한다 — MCP부터 시작해서 망가뜨려 본 3개월간의 배움
요약
Claude Code의 성능을 극대화하기 위해 MCP(Model Context Protocol)가 아닌 CLAUDE.md부터 시작하는 5단계 하네스 구축 순서를 제안합니다. 무분별한 MCP 연결은 도구 선택 오류를 유발하므로, 규칙과 스킬을 먼저 정의한 후 외부 연결을 추가해야 합니다.
핵심 포인트
- Claude Code의 자율도는 모델이 아닌 사용자 설정(Harness)에 의해 결정됨
- 하네스 구축 순서: CLAUDE.md → hooks → skills → sub-agents → MCP
- MCP를 최상위 층에 먼저 배치할 경우 도구 선택 오류(Hallucination) 발생 위험 급증
- 효율적인 운영을 위해 3개월 단위의 하네스 재고 조사와 정비가 필수적임
- Claude Code의 자율도는 기본 모델이 아니라 하네스(Harness, 사용자 측 설정)에 의해 결정된다. 밑바닥부터 쌓지 않으면 무너진다.
- 쌓는 순서는 CLAUDE.md / AGENTS.md → hooks → skills → sub-agents → MCP의 5개 층이다. MCP부터 시작하는 것이 가장 망가지기 쉽다. - "Claude가 갑자기 멍청해졌다"고 느껴진다면, 먼저 CLAUDE.md와 hooks를 재검토하라. MCP를 건드려도 고쳐지지 않는다.
3개월 전, 나는 MCP 서버 4개를 동시에 연결하고 "이것으로 최강이다"라고 생각했다. 한 세션에서 사용할 수 있는 도구가 17개에서 127개로 불어났고, Claude Code가 지시를 잊거나 무관한 외부 시스템을 호출하는 바람에 일주일 후에는 MCP를 전부 제거했다.
참고한 1차 자료:
- Claude Code 공식 문서 — Hooks
- Claude Code 공식 문서 — Sub-agents
- Claude Code 공식 문서 — Skills
- Model Context Protocol 사양 (modelcontextprotocol.io)
- Anthropic Engineering Blog — Claude Code의 구현 패턴
순서를 틀리면 하네스는 효과가 없다. 밑바닥부터 쌓으면 효과가 있다. 오늘은 그 쌓는 순서를 각 층에서 무엇을 하는지를 포함하여 전부 작성한다.
- 왜 "밑바닥부터 쌓는가" —— MCP부터 시작했던 나의 실패
- 제1층: CLAUDE.md / AGENTS.md (최박형 규칙 층)
- 제2층: hooks (기계적 강제 층)
- 제3층: skills (재사용 패턴 층)
- 제4층: sub-agents (역할 분리 층)
- 제5층: MCP (외부 연결 층)
- 3개월마다의 재고 조사 —— 쌓아 올린 것은 부패한다
- 자주 빠지는 함정
- 요약 —— 오늘/이번 주/이번 달의 액션
2026년 2월, Claude Code를 사용하기 시작한 지 1개월 만에 "MCP (Model Context Protocol)가 공식 기능이다"라는 것을 알게 되었다. Slack, GitHub, Notion, Linear, 4개의 MCP 서버를 settings.json에 등록했다 (MCP 공식 레퍼런스 구현과 서드파티 제품을 혼합).
// 개념 예시 (패키지명은 버전에 따라 달라지므로 공식 리포지토리 참조)
{
"mcpServers": {
...
기동하면 Claude Code는 127개의 도구를 가진 상태로 세션을 시작한다 (표준 17개 + Slack 32개 + GitHub 41개 + Notion 19개 + Linear 18개). "오늘의 Slack을 봐줘"라고 말하면, Slack이 아니라 Notion을 호출했다. "GitHub PR을 올려줘"라고 말하면, Linear에 이슈를 생성했다.
실측으로 기록한 에러는 다음과 같다:
Error: Unable to find issue with ID "PR-1234" in Linear workspace
(의도: GitHub PR #1234를 확인하고 싶었다)
Error: Notion page not found: "today's standup"
(의도: Slack의 오늘 standup 채널을 보고 싶었다)
50세션 분량의 로그를 취한 결과, 도구 선택을 실수하는 빈도가 31% (154회 중 48회)에 달했다. 31%란 "3번에 1번은 의도와 다른 외부 시스템을 호출한다"는 수준으로, 인간이 계속해서 팔로업한다는 전제가 없으면 운영할 수 없다. MCP를 전부 제거하자 이 수치는 0%로 돌아갔다.
그로부터 1개월에 걸쳐, izanami 씨와 Komte 씨의 운용 기사(2026년 5월)와 자신의 Cockpit 구현 로그를 대조하며 하네스를 밑바닥부터 다시 쌓았다. 결론은 심플하다. MCP는 "이미 자율적으로 움직일 수 있는 하네스"의 최상위에 올리는 것이며, 바닥부터 쌓지 않으면 의미가 없다.
아래가 쌓는 순서다.
| 층 | 역할 | 두께의 기준 |
|---|---|---|
| 1. CLAUDE.md / AGENTS.md | 규칙 · 문맥 | 루트 얇음 / 서브 두꺼움 |
| ... |
각 층을 차례대로 살펴보겠다.
CLAUDE.md는 Claude Code가 세션 시작 시 반드시 읽는 파일이다. ~/.claude/CLAUDE.md (글로벌)와 ./CLAUDE.md (프로젝트) 양쪽이 계층적으로 읽힌다.
처음 저지르는 실수는 "루트(root) CLAUDE.md에 전부 적는 것"이다. 프로젝트 전체 규칙, 테스트 명령어, 린트(Lint) 설정, 도메인 지식을 파일 하나에 몰아넣으면, 무관한 파일을 편집할 때도 전체 내용이 문맥(Context)에 포함된다.
해결책은 간단하다. 루트는 얇게, 서브 디렉터리(Sub-directory)는 두껍게 작성하는 것이다.
project/
├── CLAUDE.md # 5~10행. 프로젝트 목적과 루트 규약만 포함
├── frontend/
...
루트 CLAUDE.md의 내용은 예를 들어 다음과 같이만 있어도 충분하다:
# 프로젝트 규약
- 각 서브 디렉터리의 CLAUDE.md를 반드시 읽을 것
- main 직후 push 금지, 브랜치를 통한 PR 필수
...
Claude Code는 인덱스를 만들지 않고 grep으로 탐색한다. 깊은 위치에서 실행하는 것이 무관한 파일로 인한 노이즈를 줄이는 길이다. cd backend/ && claude 방식이, 루트에서 실행한 뒤 cd backend/를 하는 것보다 빠르다.
AGENTS.md는 OpenAI Codex CLI 등 다른 에이전트형 도구와 공통으로 사용하는 규약 파일이다. Claude Code 단독으로 사용한다면 CLAUDE.md만으로 충분하다. 두 가지를 모두 사용한다면 CLAUDE.md에서 @AGENTS.md로 참조하게 하는 구조로 만들면 중복을 없앨 수 있다.
내 환경에서는 mine/CLAUDE.md는 @AGENTS.md 한 줄, mine/AGENTS.md도 10행 미만으로 운영하고 있다.
CLAUDE.md는 "읽으면 알 수 있는" 레이어이고, hooks는 "자동으로 발화하는" 레이어다. CLAUDE.md에 적어두어도 잊어버릴 때가 있지만, hooks는 확실하게 동작한다.
Claude Code는 공식 문서에서 7종의 hook 이벤트를 공개하고 있다.
| 이벤트 | 발화 타이밍 | 용도 예시 |
|---|---|---|
| SessionStart | 세션 시작 시 | git status 표시, 상한 체크 |
| ... |
~/.claude/hooks/block-destructive.sh를 만든다:
#!/usr/bin/env bash
# PreToolUse hook: Bash 도구의 파괴적인 명령어 차단
set -euo pipefail
...
settings.json에 등록:
{
"hooks": {
"PreToolUse": [
...
CLAUDE.md에 "rm -rf 금지"라고 적어두어도, 4시간 뒤의 세션에서는 잊어버릴 수 있다. hook이라면 잊지 않는다.
외부 URL을 반환할 때, 공개된 글에 죽은 URL(Dead URL)이 섞여 있는 것은 치명적이다. Stop hook으로 직전 응답을 스캔하여 죽은 URL을 경고하는 메커니즘을 넣어두었다.
#!/usr/bin/env bash
# ~/.claude/hooks/check-urls.sh
last_response=$(cat)
...
이 hook이 울리면 즉시 응답을 수정하는 방식으로 운영하고 있다.
현재 내 hook의 개수는 14개다. 너무 많으면 실행이 느려지고, 너무 적으면 강제력이 약하다. **"같은 실수를 3번 하면 hook으로 만든다"**라는 규칙으로 운영 중이다.
실측값:
- hook 0개 → 세션 시작 0.4초
- hook 14개 (그 중 SessionStart 3개, PreToolUse 5개, Stop 2개, PostToolUse 4개) → 세션 시작 1.8초
- hook 30개까지 늘렸을 때의 실험 → 세션 시작 4.3초 (체감상 느림)
20개가 넘어가면 체감 속도가 떨어진다. 15개 전후가 운영하기 적당하다.
skills는 "특정 지시 패턴을 이름으로 부를 수 있는" 기능으로, 공식 문서에 사양이 나와 있다. /qiita, /verify, /conductor와 같이 슬래시 커맨드(Slash command)로 호출하거나, 자연어를 통해 Claude가 자동으로 실행한다.
~/.claude/skills/<skill-name>/SKILL.md에 위치시킨다. frontmatter에 description을 작성하면, Claude가 "이 요청에 이 skill이 해당한다"라고 자동으로 판단한다.
---
name: verify
description: 사실·URL·출처 검증 전용 스킬. 응답에 포함하기 전에 curl 생존 확인 + WebFetch 내용 확인을 강제하며, 검증되지 않은 주장에는 [unverified] 태그를 붙임. /verify 로 실행.
...
이 skill은 "URL을 붙여넣기 전에 교정하라"라는 규칙을 CLAUDE.md에 적어두는 것만으로는 잊기 쉬운 부분을, 명시적으로 호출할 수 있는 동작 패턴으로 고정화한다.
"동일한 절차를 3회 이상 반복한다" → skill화
"절차가 10단계 이상이다" → skill화
"실수하면 치명적이다 (공개 기사/운영 환경 배포)" → skill화
나의 현재 상태는 21개다. 많지만, 각각의 역할이 명확하고 중복되지 않는다.
- 1개의 skill에 5개의 목적을 섞는다 → 기동 조건이 모호해져 자동 기동이 작동하지 않음
- frontmatter의
description이 추상적이다 → Claude가 "해당함"이라고 판단할 수 없음 - skill끼리 동일한 파일을 작성한다 → 충돌하여 결과가 불안정해짐
Claude Code는 여러 개의 sub-agent를 전환하며 사용하는 구조를 가진다 (공식 문서 — Sub-agents). ~/.claude/agents/<agent-name>.md에 역할 정의를 작성한다.
---
name: reviewer
description: 코드 리뷰, 경미한 수정, git commit, PR 작성을 수행하는 에이전트
...
한 명의 Claude가 "설계 + 구현 + 리뷰 + 배포"를 전부 수행하면, 관점이 섞여 리뷰가 느슨해진다. 리뷰만을 담당하는 sub-agent를 별도로 세우면, 구현한 본인과는 다른 관점에서 볼 수 있다.
나의 구성 예시:
| 에이전트 | 역할 |
|---|---|
| builder | 설계・구현・테스트・버그 수정 |
| ... |
team-lead가 오케스트레이터 (Orchestrator) 역할을 하며, 필요에 따라 다른 에이전트를 병렬로 기동한다. 이것만으로도, 1개 세션에서 "병렬 조사 → 설계 → 구현 → 리뷰"가 돌아간다.
여러 sub-agent를 기동할 때, 독립된 태스크는 병렬로 던진다. 하나의 메시지로 여러 개의 Agent tool call을 내보낸다. 직렬로 던지면, 동일한 조사를 대기 시간을 포함하여 반복하게 된다.
내가 직접 만든 병렬 오케스트레이터에서는, 5개의 리서치를 병렬로 실행함으로써 직렬 17분 → 병렬 2분 53초, 즉 5.9배속을 실측했다. 동일한 패턴은 sub-agent에서도 그대로 적용된다.
여기서 드디어 MCP다. MCP는 Claude Code 외부의 시스템 (Slack, GitHub, Notion, Figma, 각종 DB)에 표준 프로토콜로 접속한다.
MCP는 도구를 늘려주지만, 도구가 늘어나면 Claude의 판단 노이즈도 늘어난다. 1개 세션에 MCP 도구가 127개 있는 상태는, 인간으로 치면 "책상 위에 127개의 도구가 늘어서 있는" 상태로, 선택 실수가 반드시 발생한다. 서두에 쓴 31%의 선택 실수율이 실측값이다.
하위 4개 층이 정돈되어 있다면, "Slack에 보낼 때는 이 순서, GitHub에 PR을 낼 때는 이 규약"이 고정되어 있다. MCP는 그 규약 위에 올라탈 뿐이므로, 선택의 여지는 줄어든다.
그 외부 시스템을 주 3회 이상 사용하는가? → Yes라면 도입
CLAUDE.md에 "이 외부 시스템을 사용할 때의 규약"이 적혀 있는가? → Yes라면 도입
대응하는 hook나 skill로 규약을 강제할 수 있는가? → Yes라면 도입
3개 모두 No라면 도입을 보류한다. 주 1회밖에 사용하지 않는 Notion을 MCP로 연결하더라도, 매 세션마다 120개 도구의 노이즈를 제거하는 비용이 더 높다.
나 자신도 MCP는 소비(기성 서버 이용)일 뿐, 직접 제작한 경험은 제로다. 이것이 하네스(Harness)의 최상위 공백이며, 다음 축적 대상이 되고 있다. FastMCP 등 Python 기반 프레임워크로 Hello World까지는 동작하지만, 실제 운영 환경에 올린 경험은 아직 없다.
이 부분을 채우면 하네스는 완성되지만, 하위 4개 층이 정돈되지 않은 상태에서 직접 만든 MCP로 뛰어드는 것은 또다시 같은 실패를 반복할 뿐이다.
하네스는 쌓아 올리는 것뿐만 아니라, 정기적인 유지보수가 필요하다. 이유는 세 가지다.
오래된 기술과 새로운 운영의 모순: 3개월 전의 CLAUDE.md에는 "A를 사용하라", 6개월 전의 skill에는 "B를 사용하라"고 적혀 있으면, Claude가 오래된 쪽을 채택하여 동작이 저하됨
발화하지 않는 hook이 남음: 한 번밖에 사용하지 않은 hook이 기동 비용을 계속 소모함
중복되는 skill이 증가함: qiita-post와 qiita-publish와 qiita-auto-draft가 병행되고 있어, 어떤 것을 기동할지 Claude가 망설임
| 카테고리 | 체크 항목 |
|---|---|
| CLAUDE.md / AGENTS.md | 300행 초과 시 중복/모순 체크. 루트(Root)는 얇게 유지되고 있는가 |
| ... |
# memory의 최종 업데이트 날짜순
ls -lat ~/.claude/projects/*/memory/*.md | tail -20
# 과거 N일 동안 실행되지 않은 hook을 검출 (jsonl 로그 전제)
...
재고 조사 결과를 harness_audit_YYYY_MM.md로 남겨두면, 삭제/통합한 변경 이력이 된다.
나의 다음 재고 조사 예정일은 2026년 8월. 3개월 뒤로 /schedule을 통해 등록했다.
실제로 빠졌던 함정 5가지.
~/.claude/CLAUDE.md를 412행으로 만들어, 매 세션마다 풀 로드(Full load)했던 시기가 있었다. 1 세션당 추정 3,200 토큰(전체 약 12KB)이 매번 앞에 배치되어 흐르게 되었고, 관계없는 규칙들이 문맥(Context)에 계속 포함되었다.
대처: 글로벌(Global)은 '모든 프로젝트 공통의 절대 규칙'만(50100행, 약 8001,200 토큰) 두고, 각 프로젝트는 ./CLAUDE.md에서 고유 규약을 작성한다. 현재 나는 78행으로 운용하고 있으며, 기동 시간이 체감상 절반 이하로 줄었다.
- hook의 출력은 JSON으로,
{"decision": "block", "reason": "..."}와 같은 구조를 반환해야 한다.printf로Error: ...와 같은 생 문자열(Raw string)을 내뱉으면, Claude Code 측에서 무시되어 hook이 작동하지 않는 것처럼 보인다.
실제로 겪은 에러:
$ ./block-destructive.sh < test-input.json
Error: command blocked
$ echo $?
...
대처: hook의 출력은 반드시 jq로 검증한 후 본 작업에 투입한다.
echo '{"decision":"block","reason":"test"}' | jq . > /dev/null && echo OK
description: "편리한 기능"이라고 써도, Claude는 어떤 요청에서 기동해야 할지 판정할 수 없다.
대처: description에 「언제 기동하는지」를 구체적으로 적는다.
description: "Qiita 기사를 자동 생성 및 게시하는 스킬.
소재 선정 → 조사 → 집필 → 품질 게이트 → 게시까지의 일관된 파이프라인.
『기사 써줘』 『Qiita 게시』로 기동."
트리거가 되는 발화 예시까지 적으면 자동 기동의 정밀도가 올라간다.
tools: *로 모든 도구를 허용하면, 에이전트(Agent)가 본래의 역할에서 벗어난다. reviewer 에이전트가 WebFetch로 관계없는 사이트를 보러 가기 시작한 적도 있다.
대처: 각 에이전트의 tools:를 화이트리스트(Whitelist)로 관리한다. reviewer라면 Read, Grep, Glob, Bash, Edit만, researcher라면 Read, Grep, Glob, WebSearch, WebFetch만 허용한다.
- MCP 도구는 독자적인 프로토콜로 작동하기 때문에, Bash용으로 작성한 PreToolUse hook은 발화하지 않는다. MCP로
notion_create_page를 호출했을 때의 안전 확인 hook은 별도로 준비해야 한다.
대처: MCP 도구용 matcher를 별도로 작성한다.
{
"hooks": {
"PreToolUse": [
...
하네스(Harness)는 밑바닥부터 쌓아야 한다. MCP부터 시작하지 마라.
~/.claude/CLAUDE.md를 열어 행수를 확인한다. 300행이 넘으면 중복 부분을 삭제할 후보로 표시한다. 프로젝트에./CLAUDE.md가 없다면 5행 내외로 만든다 (프로젝트 목적, 테스트 명령어, 하나의 절대 규칙).~/.claude/hooks/에 최소 1개의 hook을 둔다.block-destructive.sh가 가장 효과적이다.- 기존 skill의
description을 재검토한다. 「언제 기동하는지」가 적혀 있지 않은 skill은 다시 작성한다. - sub-agent를 역할별로 최소 3개(builder / reviewer / researcher) 만든다.
- MCP를 사용 중이라면, 각 MCP에 대응하는 hook 또는 skill로 규약을 1개 작성한다.
- 재고 조사 예정일을
/schedule또는 캘린더에 3개월 뒤로 등록한다.
하네스(Harness)는 "만들면 끝"이 아니다. 3개월 뒤에 다시 망가진다. 그럴 각오로 운용해야 한다.
나 자신도 아직 자작 MCP를 작성하지 못했다. 다음 3개월 안에 한 권(한 권 분량의 코드/가이드)을 쓸 수 있다면, 하네스 5개 층을 전부 채울 수 있다. 쓸 수 있게 되면 다시 글로 남기겠다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기