
Claude Code의 컨텍스트 팽창으로 인한 토큰 소모 문제를 MCP 서버로 해결한 이야기
요약
Claude Code 사용 시 발생하는 컨텍스트 팽창과 토큰 소모 문제를 MCP 서버와 VSCode 확장을 통해 해결한 사례를 다룹니다. 세션 간 설계 방침과 판단을 유지하면서도 토큰 효율을 높이는 자동화된 개발 루프 구축 과정을 설명합니다.
핵심 포인트
- Claude Code의 컨텍스트 누적으로 인한 급격한 토큰 소모 문제 분석
- MCP 서버(handoff-mcp)를 활용한 세션 간 판단 및 설계 방침 인계
- VSCode 확장을 통한 태스크 진행 상황 및 작업 시간 시각화
- 컨텍스트 초기화(/clear)와 구현 일관성 유지 사이의 트레이드오프 해결
어떤 이야기인가
Claude Code로 개인 개발을 하다 보면, 매일 아침 세션을 열 때마다 똑같은 설명을 다시 해야 한다. 어제 결정한 설계 방침을 기억하지 못한다. 한 번 거절했던 방식을 다시 채택한다. 정한 명명 규칙(Naming Convention)과 다른 코드를 작성한다.
이러한 "쌓이지 않는" 문제를 해결하기 위해, 세션 간의 판단만을 인계하는 MCP 서버 (handoff-mcp)와 진행 상황을 시각화하는 VSCode 확장 프로그램 (handoff-vscode)을 만들었다.
무엇이 바뀌었나
Before: /clear
로 리미트(Limit) 대책을 세움 → 다음 날 Claude Code가 어제의 설계 방침을 잊어버려 구현이 흔들림. 수동 메모로 보완하려 했으나, 메모의 양이 늘어남에 따라 메모 자체를 관리하는 것이 번거로워져 파탄에 이름.
After: 세션을 여는 것만으로 어제의 판단이 자동으로 돌아온다. "계속해줘"라는 한마디로 방침의 흔들림 없이 구현이 진행된다. VSCode의 대시보드에서 태스크 진행 상황, 간트 차트(Gantt Chart), 작업 시간을 한눈에 알 수 있다. 자는 동안 자동 개발 루프(Automatic Development Loop)를 통해 태스크가 소화된다.
계기는 주간 리미트 대책인 /clear
를 사용하다가 겪은 "구현이 흔들리는" 문제였다. 거기서부터 한 달 반에 걸쳐 기능이 성장했다. 판단의 인계에서 시작하여, 메모리의 자동 주입, 태스크 관리, 문서 관리, 자동 개발 루프로 확장된 경위를 기술한다.
컨텍스트 1M에 기뻐했더니 3일 만에 리미트에...
최근 Claude Code의 컨텍스트(Context) 상한이 1M으로 상향되어, 솔직히 매우 기뻤다. 대규모 리팩터링 (Refactoring)을 한꺼번에 통과시킬 수 있고, 여러 파일을 넘나드는 설계 변경도 한 세션에서 몰아서 할 수 있다. "이것으로 드디어 본격적으로 개발에 사용할 수 있겠다"라고 생각했다.
실제로 할 수 있는 범위는 체감상 완전히 달랐다. 다만, 토큰(Token) 소비가 엄청났다. 1M의 컨텍스트를 풀(Full)로 사용하는 세션을 돌리다 보면, 일주일 치 리미트가 3일 만에 녹아 없어지는 일도 허다했다. 솔직히 Max $200 플랜으로 갈아탈까 진지하게 고민했다. 하지만 역시 월 3만 엔(약 3만 원 이상)을 넘기면 아내에게 크게 혼난다.
그래서 냉정하게 /context
를 입력해 내용을 살펴보니, 1M 컨텍스트 중 대부분이 과거의 대화 이력과 도구 정의(Tool Definition)로 채워져 있었다. 지금 작성하고 있는 기능과 관련된 부분은 눈대중으로 1할도 되지 않았다.
LLM의 추론은 스테이트리스(Stateless)이며, 채팅이 "대화"처럼 보이는 이유는 매 요청(Request)마다 과거의 주고받은 내용 전문을 재전송하고 있기 때문이다. 즉, 다음과 같은 구조로 되어 있다.
[1회차] system + CLAUDE.md + 도구 정의 + 발언 1
[10회차] system + CLAUDE.md + 도구 정의 + 발언 1~9 + 응답 1~9 + 발언 10
[30회차] 위와 동일 + 발언 1~29 + 응답 1~29 + 발언 30 ← 단 몇 줄의 지시사항이라도 이력이 전부 실림
1회차당 소비량이 대화가 길어지는 만큼 늘어난다. 누적치는 눈덩이처럼 불어난다. 세션을 길게 유지할수록 리미트를 소모하는 속도가 가속화되는 구조인 듯하다.
/clear를 철저히 했더니, 이번에는 구현이 흔들리기 시작했다
대책은 간단했다. 태스크가 일단락될 때마다 /clear
로 이력을 버리도록 했다. 효과는 극적이었고, 주 중반에 바닥나던 리미트가 버텨주게 되었다.
그런데 몇 주가 지나자 또 다른 위화감이 느껴졌다. 구현이 흔들린다.
- "에러 타입은 라이브러리 계층은 thiserror, 앱 계층은 anyhow로 한다"라고 결정한 다음 날,
/clear
이후의 Claude가 fn load(&self) -> Result<Session, Box<dyn Error>> 같은 시그니처(Signature)를 쓰기 시작한다.
- 한 번 검토하고 거절했던 폴링(Polling) 방식을 다음 날 다시 제안한다.
- 어제와 오늘 명명 방식(Naming Style)이 미묘하게 다르다.
생각해 보면 당연한 일이다. /clear
는 "토큰을 잡아먹는 이력"과 함께 "쌓아온 판단"도 버린다. 리미트 대책과 구현의 일관성이 동일한 하나의 이력에 실려 있었다. "리미트를 챙길 것인가, 품질을 챙길 것인가"의 이지선다가 되어버린 것이다.
"기록해둘 장소"를 한 달 반 동안 전전했다
여기서부터 /clear
를 해도 판단이 살아남을 수 있는 장소를 찾아 전전했다. 상세한 내용은 생략하고 결론만 쓰자면, 전부 안 됐다.
** /resume로 이전 세션을 복원한다** → 판단의 흔들림은 사라지지만, 대화 로그의 전문을 복원하므로 불어난 이력도 그대로 돌아온다. 몇 시간 만에 리미트 문제가 재발하여 원점으로 돌아간다.
CLAUDE.md에 기록하기 → 처음에는 효과가 있었지만, "결정한 것은 전부 추가한다"를 계속했더니 250행까지 팽창했다. CLAUDE.md는 매 요청마다 주입되므로, 줄이려 했던 토큰을 이번에는 상주하며 소모하기 시작했다. 게다가 "이번 주 할 일"과 같이 신선도가 중요한 정보는 며칠 만에 쓸모없어지고, 오래된 기술이 다음 세션에서 잘못된 유도를 하는 사고도 발생했다. 결국 150행까지 줄여서 "변하지 않는 것" 전용으로 되돌렸다.
수동 인수인계 메모 → docs/handoff/에 날짜별 메모를 작성하고, /clear
전에 "판단을 써내고", 나중에 "읽고 나서 재개하라". 이것이 가장 효과적이었다. 하지만 3주 만에 파탄 났다. 구현을 몰아붙이는 중에 손을 멈추고 메모를 작성할 여유는 없었고, "나중에 몰아서 써야지"라며 방치하다가 잠들었다. 다음 날 아침 세션이 중간에 멈춰 있었고, 2시간 동안 공들인 리팩터링 (Refactoring) 방침을 인수인계 메모에 쓰기 위해서만 400k가 넘는 컨텍스트 (Context)를 통째로 다시 읽어 들여야 했다. 메모를 쓰기 위해 리미트 (Limit)를 거대하게 낭비하는, 대체 무엇을 위한 메모인지 알 수 없는 상태가 된 것이다. 그렇다면 CLAUDE.md에 "세션 종료 시 인수인계 메모를 작성하라"고 써두면 되지 않느냐고 생각할 수도 있겠지만, 그렇게 해봤다. 해봤는데도 안 됐다. Claude Code는 쓰는 것을 잊어버리고, 써도 입도가 제각각이며, 무엇보다 작성 기준을 정해두지 않으면 대화 로그의 요약 같은 메모가 만들어져 불필요한 정보까지 인수인계에 포함된다. 그 자체로 토큰 낭비이며 다음 세션의 컨텍스트도 오염시킨다. 근본적으로 인간이 절차를 관리하는 한 파탄 날 수밖에 없는 구조였다.
/resume
, CLAUDE.md, 수동 메모. 전부 시도해보고 알게 된 것은, 남겨야 할 것에 대한 답은 처음부터 나와 있었다는 것이다. 대화 로그의 전문이 아니라, 판단의 결론만. 전문은 부풀어 올라 리미트를 낭비하지만, 결론만이라면 수백 토큰으로 끝난다. 막혀 있던 것은 "무엇을 남길 것인가"가 아니라, 작성과 읽기를 인간이 수동으로 하고 있다는 점이었다. 잊어버리는 것도, 소실되는 것도, "읽는" 수고도 전부 거기서 비롯되었다.
그렇다면, Claude 스스로 하게 만들면 된다.
판단의 결론만 인수인계하는 MCP 서버를 만들었다
그것을 실현할 수 있는 메커니즘이 MCP (Model Context Protocol)로, 알고 있듯이 Claude Code와 같은 에이전트 (Agent)가 에디터 외부의 도구를 호출할 수 있게 하는 공통 규격이다. 요컨대 직접 서버를 작성하면 원하는 데이터의 읽기/쓰기를 Claude Code에게 맡길 수 있는 것이다.
이를 사용하여 수동 메모 운용을 그대로 자동화하는 MCP 서버를 6월 13일부터 작성하기 시작했다. 세션 시작 시 Claude Code가 handoff_load_context를 호출하여 이전의 판단을 읽어 들이고, 작업의 매듭마다 .handoff/ 하위로 저장해 나간다. 전부 로컬 파일이며, 클라우드에는 아무것도 보내지 않는다.
사용 편의성은 다음과 같이 바뀌었다.
Before — 수동 메모 시대:
> docs/handoff/2026-06-05.md를 읽고 나서 작업을 시작해줘
(매번 직접 지시. 잊어버리면 흐트러짐)
After — 세션을 열면 Claude Code가 알아서 수행:
> 지난번 작업 이어서 해줘
Claude Code: 이전 컨텍스트를 읽어 들였습니다.
결정: 에러 타입은 thiserror/anyhow의 2층 구조
...
실제로 무엇이 저장되는가
이것은 어제, 이 MCP 자체의 개발 세션이 끝났을 때 저장된 실제 데이터 (발췌).
{
"summary": "weighted BM25 전환 + recall 검증 구현 및 커밋 (0dbb64f, v0.24.8)",
"branch": "feat/weighted-bm25-memory-query",
...
보시는 바와 같이, 대화 로그는 한 줄도 들어있지 않다. 무엇을 결정했는지, 왜인지, 어디까지 검증되었는지. 계속 부풀어 오르던 이력 대신, 다음 세션은 이것만을 읽고 재개한다. 읽기 작업은 수백 토큰이므로 리미트 문제로 되돌아가지 않는다.
confidence 필드를 추가한 이유는, 이전 세션에서 "검증해서 이걸로 가자"라고 결정한 것인지, "일단 이걸로 진행하자"라고만 한 것인지가 다음 날에는 구분이 안 되었던 경험 때문이다. 미검증 가설 (unverified) 이 다음 날 "확정 사항"인 것처럼 구현에 섞여 들어와 나중에 재작업(Retake)이 발생하는 것이 수동 메모 시대의 숨겨진 변동 요인이었다.
설계는 실패의 반사
한 달 반 동안 떠돌며 겪은 실패를 그대로 설계에 투영했다.
- "마지막에 한꺼번에 작성한다"는 방식 때문에 2시간 분량이 날아갔기에, 저장은 매 단계의 차분 (diff) 단위로 했다. 중간에 세션이 끊겨도 직전 단계까지는 남는다. - 250행에 달하던 CLAUDE.md의 반성으로부터, "에러 타입은 thiserror/anyhow를 사용한다"와 같은 영구적인 규칙은 **메모리 (memory)**로서 별도로 분리했다. 모든 규칙을 매번 주입하는 것이 아니라, 관련 파일을 건드렸을 때 관련 있는 것만 주입되도록 했다. - 수동 메모의 "다음에 할 일" 칸이 사실상의 태스크 리스트(task list)화 되어 있었기에, 태스크 상태는 메모의 부속물이 아닌 일급 데이터 (first-class data)로 취급했다.
판단은 이어받을 수 있었다. 하지만 규칙이 지켜지지 않는다
세션 인계(session handoff)를 통해 "어제의 작업 내용"은 통하게 되었다. 하지만 며칠간 사용하며 깨달은 것은, 규칙이 지켜지지 않는 문제가 여전히 남아 있다는 점이었다.
250행에서 150행으로 줄인 CLAUDE.md에는 "변하지 않는 것"만 적혀 있다. 그렇다면 쫓겨난 "thiserror/anyhow 방침"과 같은 프로젝트 고유의 규칙은 어디에 있는가? 세션 인계 시의 결정 사항(decisions)에 매번 넣기에는 너무 장황하고, 메모리로 별도 저장하더라도 Claude Code가 handoff_memory_query를 호출하는 것을 잊어버린다면 그 규칙은 존재하지 않는 것과 마찬가지다.
여기서 벽이 된 것이 MCP의 구조적인 제약이었다. MCP는 client $\rightarrow$ server 방식의 일방향 요청/응답 구조이기에, 서버 측에서 Claude Code의 추론에 개입할 수 있는 수단이 없다. Claude Code가 "메모리를 읽어야겠다"라고 생각하지 않는 한, 아무리 규칙을 저장해 두어도 아무 일도 일어나지 않는다.
Claude Code에도 /memory로 사용할 수 있는 사용자 단위의 메모리 기능이 있지만, 그것은 글로벌한 개인 메모리다. "이 프로젝트의 이 파일을 건드렸을 때만 이 규칙을 주입한다"와 같은 스코프(scope)가 지정된 자동 주입은 불가능하다.
해결책은 Claude Code의 hook이었다. hook은 프롬프트 전송이나 파일 편집이 일어날 때마다 Claude Code의 의사와 상관없이 실행되는 스크립트다. 여기서 handoff_memory_query를 호출하여, 현재 다루고 있는 파일의 경로 나 프롬프트의 키워드에 BM25 (텍스트 검색의 가중치 스코어링) 방식으로 매칭되는 메모리만 자동으로 주입하는 memory hook을 만들었다. 에러 처리 파일을 열면 thiserror/anyhow 규칙이 자동으로 들어온다. 테스트 코드를 작성하면 테스트 규약이 들어온다. Claude Code가 스스로 "읽어야겠다"라고 생각할 필요가 없어진 것이다.
이 메커니즘을 도입한 이후, /clear 이후의 구현 품질이 한 단계 더 안정되었다. 판단의 인계가 "세션 간의 편차"를 없애고, 메모리의 자동 주입이 "규칙 이탈"을 없앤다. 두 개의 계층이 각각 별개의 문제를 막아주고 있다.
공정 관리가 필요해졌다
세션과 메모리를 통해 "무엇을 만들 것인가"는 안정되었다. 다음에 곤란해진 것은 "얼마나 시간이 걸리고 있는지 알 수 없다"는 점이었다.
수동 메모의 "다음에 할 일" 칸이 태스크 관리 도구로 성장한 것은 좋았지만, "이 기능 구현에 결국 몇 시간이 걸렸는가?"를 알 수 없었다. 우선 Claude Code가 handoff_timer_start / stop을 호출하게 하여 태스크별 작업 시간을 자동으로 측정하도록 해보았다. 예상 시간도 함께 갖게 하여, 예상치 vs 실적의 괴리를 볼 수 있게 했다. 다만 Claude Code의 예상 시간은 "사람이 하면 몇 시간"을 기준으로 나오기 때문에, Claude Code와 함께 작업하면 실제로는 그 5분의 1 정도 만에 끝난다. 이 차이를 방치하면 공정이 늘어지기 때문에, 보정 계수를 곱한 조정된 시간도 출력하도록 했다.
태스크가 30개를 넘어갈 무렵, 이번에는 JSON 목록만 봐서는 "언제까지 무엇이 끝나는지" 파악할 수 없게 되었다. 그래서 시각화하기 위해 VSCode 확장 프로그램에 간트 차트(Gantt chart)를 추가했다. 태스크를 드래그하여 일정을 재조정(reschedule)하고, 의존 관계 선, 달력을 고려한 자동 스케줄링, 일별 부하 히트맵(heatmap)까지 넣었다. JSON을 바라보던 시절과는 완전히 다른 관점을 제공한다.
사양서가 구현에 뒤처지다
구현이 진행됨에 따라 다음에 드러난 문제는 사양(specification)과 설계, 그리고 태스크가 서로 연결되어 있지 않다는 점이었다.
Claude Code에게 사양서(specification)를 작성하게 하는 것은 꽤 능숙하게 해낸다. 하지만 구현이 어느 정도 진행되다 보면, 사양을 업데이트하지 않고 구현이 앞서 나가는 경우가 자주 발생한다. 정신을 차려보면 사양서가 노후화되어 있고, 다음 세션에서 오래된 사양을 근거로 구현을 시작하게 된다 — CLAUDE.md 250행 문제와 동일한 구조의 재발이다.
문제는 "사양서의 어느 섹션이 설계서의 어디에 대응하는지, 그리고 그와 연결된 태스크가 무엇이며 어디까지 진행되었는지"를 횡단적으로 추적하는 것이 번거롭다는 점이다. 사양·설계·태스크가 서로 다른 파일에 흩어져 있으면 인간이 수동으로 추적(trace)할 수밖에 없다.
그래서 handoff에 문서 관리 기능을 추가하여, 사양서·설계서·ADR (Architecture Decision Record, 설계 결정 기록)을 .handoff/docs/에 저장하고 태스크와 양방향으로 링크되도록 했다. 사양의 섹션별로 "검증됨 / 미검증 / 수정 필요" 플래그를 달 수 있는 handoff_doc_verify와, 태스크 체크리스트 + 사양의 검증 상황을 통합한 handoff_task_checklist를 도입했기에, "이 태스크를 완료(done)해도 되는가"를 사양의 뒷받침 근거와 함께 판단할 수 있다. 문서 간의 부모-자식 관계나 참조를 따라갈 수 있는 handoff_doc_graph도 있어, 사양→설계→태스크의 추적성(traceability)이 끊어지지 않게 되었다.
자는 동안 태스크가 완료된다
세션 인계, 메모리 자동 주입, 태스크 관리, 문서화. 여기까지 갖춰지니 다음에 든 생각은 "이 모든 것이 연결되어 있다면, 인간이 자는 동안 루프(loop)를 돌릴 수 있지 않을까?"였다.
Claude Code 자체에도 /loop 명령어로 명령을 반복하는 기능이 있다. 다만, 세션을 넘나드는 태스크의 소화·인계·품질 게이트(quality gate)를 일체형으로 수행하려면 handoff의 데이터 모델과 맞물려야 했다. 그래서 handoff의 태스크 큐를 직접 사용하는 자동 개발 루프 (/session-loop)를 만들었다.
자기 전에 /handoff-task-loop:session-loop 남은 태스크 전부 실시해 둬라고 입력하면 루프가 돌아가기 시작한다. 먼저 태스크를 의존 관계와 기능 영역에 따라 "세션"으로 분할하고, 세션별로 실행한다. 하나의 세션 안에서는 파일 충돌이 없는 태스크에 개발 에이전트를 할당하여 **병렬 실행(parallel execution)**한다. 모두 완료되면 한꺼번에 테스트하고, 테스트를 통과하면 리뷰한다. 테스트나 리뷰에서 탈락하면 모두 스테이지 1로 되돌려 다시 시작한다. 통과하면 태스크를 완료(done) 처리하고 세션을 닫은 뒤 다음 세션으로 넘어간다. 세션 간의 인계는 handoff와 융합되어 있으므로 판단과 문맥이 끊기지 않는다.
아침에 일어나 대시보드를 열면 "어젯밤 3개 세션이 돌아갔고, 5개 태스크 완료, 1개 태스크는 최대 루프 수에 도달하여 중간에 멈춤"과 같은 상황이 보인다. 각 세션의 결정 사항(decisions)을 열어보면 도중에 무엇을 판단했는지도 추적할 수 있다.
도중에 문제가 발생할 경우 — 테스트 미통과, 사양 모호, 의존 라이브러리의 동작이 예상과 다름 — 해당 문제를 새로운 태스크로 자동 등록하고 루프를 계속 진행한다. 아침에 일어나 "자, 오늘은 무엇을 할까"라고 고민할 필요 없이, 어젯밤 생성된 문제 태스크들이 나열되어 있다. 판단이 필요한 것만 직접 처리하고, 나머지는 다시 밤에 돌리면 된다.
개인 개발에서 이것이 효과적인 경우는 "주말에만 만질 수 있는 프로젝트"다. 금요일 밤에 태스크를 쌓아두고 루프를 돌려놓으면, 토요일 아침에는 기계적인 구현이 정리되어 있어 인간은 설계 판단과 동작 확인부터 시작할 수 있다. 주말의 한정된 시간을 Claude Code가 할 수 있는 작업이 아니라, 나만이 할 수 있는 판단에 사용할 수 있게 되었다.
1개월 사용 후 어떻게 변했나
가장 변한 점은 ** /clear를 거리낌 없이 칠 수 있게 되었다**는 것이다. 판단 내용이 이력(history) 외부에 남아 있으므로, 이력은 그저 소비물로서 버려질 수 있다. "리미트를 챙길 것인가 품질을 챙길 것인가"라는 이지선다가 사라졌다.
- 주간 리미트: 몰아치는 주간에도 "리미트를 신경 쓰느라 작업을 멈추는" 일이 없어졌다.
- 구현의 일관성:
Box<dyn Error>사건과 같은 방침의 역행은 거의 보이지 않게 되었다. - 개발 속도: 도그푸딩(dogfooding)을 하며 1개월 만에 v0.24까지 진행되었다. 이 글의 작성 태스크 관리도 스스로 시키고 있다.
Claude Code가 백그라운드에서 무엇을 저장하고 있는지 보이지 않으면 "제대로 인계되고 있는 건가?" 하는 불안함이 생기므로, VSCode 확장 프로그램도 만들어 대시보드로 시각화하고 있다. 현재 내 손에 있는 화면은 이렇다.


솔직히 말하자면, 만능은 아니다. Claude Code가 '저장 시점'을 제대로 판단하지 못해 세세한 결정 사항이 하나씩 누락되는 경우가 가끔 발생한다 (수동 메모 시절에 까먹는 것보다는 훨씬 나은 수준이다). 또한, 세션이 짧거나 단발성 스크립트 용도로 사용하는 사람이라면 /resume만으로도 충분하다. Cline의 Memory Bank 계열을 사용하고 있다면, 그 방식의 흐름을 따르는 것이 더 좋다고 생각한다.
마치며
돌이켜보면, 주간 리미트(weekly limit)의 정체는 '매 요청마다 히스토리 전문을 재전송'하는 것이었고, /clear로 구현했을 때 로직이 어긋났던 정체는 '히스토리와 함께 판단(decision)까지 버리고 있었기' 때문이었다. 이 두 가지를 동시에 해결하려면, 판단 내용만 히스토리 외부로 빼내는 방법뿐이었다. 나의 경우, 그것이 MCP를 Rust로 작성하는 형태가 되었다.
직접 만든 것이라 개인용이지만, 정리해서 공개해 두었다: alphaelements/handoff-mcp (MIT). VSCode 확장 프로그램은 VSCode Marketplace에서 "Handoff MCP Dashboard"를 검색하면 설치할 수 있다. 요청이 많다면 Open VSX (Cursor 등에서도 사용할 수 있는 마켓플레이스)에도 출시할 계획이다. 내가 쓰고 싶어서 만든 것이기에, 세션을 넘나드는 개발이 계속되는 한 계속 키워나갈 것이다.
같은 두 가지 선택지 사이에서 고민하고 있는 분들은, 우선 /context를 통해 자신의 세션 내용을 확인해 보길 권한다. 히스토리와 도구 정의(tool definition)가 몇 퍼센트를 차지하고 있는지 보면 아마 놀랄 것이다. 이 문제를 어떻게 극복하고 있는지, 추천하는 방법이 있다면 댓글로 알려주면 감사하겠다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기