사용하지 않는 MCP 플러그인이 조용히 컨텍스트를 갉아먹고 있습니다: 주간 자동 비활성화 파이프라인
요약
Claude Code 사용 시 사용하지 않는 MCP 플러그인이 컨텍스트를 점유하여 토큰 예산을 낭비하는 문제를 해결하기 위한 자동화 파이프라인을 소개합니다. 30일간 호출되지 않은 플러그인을 탐지, 비활성화, 아카이빙하는 3단계 프로세스를 통해 효율적인 컨텍스트 관리를 지원합니다.
핵심 포인트
- 활성화된 MCP 플러그인은 호출 여부와 상관없이 도구 스키마를 컨텍스트에 주입함
- 사용하지 않는 플러그인이 쌓이면 Claude의 가용 토큰 예산이 감소함
- 탐지, 자동 비활성화, 캐시 아카이빙으로 구성된 3단계 파이프라인 구축
- jq를 활용하여 JSONL 로그에서 실제 tool_use 호출을 정확히 집계
이 글은 저의 "Claude Code 환경" 시리즈의 연장선입니다. git-config 백업 자동화에 관한 이전 포스트에 이어, 이번에는 은밀하게 다가오는 문제인 한 번 활성화한 뒤 잊어버린 MCP 플러그인들이 계속해서 컨텍스트 (context)를 조금씩 갉아먹는 문제를 다루며, 이를 매주 일정에 따라 자동으로 비활성화하는 3단계 파이프라인을 소개하겠습니다.
플러그인을 활성화하면, 단 한 번도 호출되지 않더라도 시작 시점에 해당 도구 스키마 (tool schema)가 주입됩니다. 이러한 "그냥 켜두는" 플러그인들이 쌓이면서, Claude가 실제로 작업에 사용할 수 있는 토큰 예산 (token budget)은 조용히 줄어듭니다. 이 글에서는 탐지, 자동 비활성화, 그리고 캐시 아카이빙 (cache archiving)을 실제 코드를 통해 하나의 흐름으로 연결하겠습니다.
문제점: 추가만 되고 삭제는 되지 않는 플러그인 관리
새로운 플러그인을 설치하려는 동기는 명확합니다. "이 도구를 사용하고 싶다"는 능동적인 욕구입니다. 하지만 플러그인을 제거하려는 데에는 그에 상응하는 동기가 없습니다. "음, 최근에 저 플러그인은 사용하지 않았네"라고 깨닫는 순간은 좀처럼 오지 않습니다.
매 세션이 시작될 때마다, Claude Code는 settings.json의 enabledPlugins 아래에 true로 표시된 모든 플러그인의 도구 스키마를 컨텍스트에 주입합니다. "활성화되어 있지만 30일 동안 한 번도 호출되지 않은" 플러그인이 많아질수록, 매 실행 시마다 컨텍스트의 상단이 기여도가 없는 스키마로 채워지게 됩니다.
몇 달 동안 이것저것 시도하며 활성화 상태로 방치하다 보면, 휴면 (Dormant) 플러그인(활성화되었으나 사용되지 않음)의 개수가 30개를 넘어설 수 있습니다.
아키텍처: 3단계 파이프라인
plugin-usage.sh → 휴면 플러그인 시각화 (수동 실행 / 주간 보고)
plugin-auto-disable.sh → 30일 동안 사용되지 않은 모든 항목을 매주 자동 비활성화
cleanup-plugin-cache.sh → 비활성화된 캐시를 .disabled-cache/로 아카이빙
plugin-auto-disable.sh apply의 끝부분이 cleanup-plugin-cache.sh apply를 체인 호출(chain-calls)하므로, launchd가 시작할 수 있는 단일 진입점이 존재합니다.
1단계 — plugin-usage.sh로 휴면 플러그인 시각화
~/.claude/scripts/plugin-usage.sh는 지난 N일(기본값 14일) 동안의 세션 JSONL로부터 실제로 tool_use로 호출된 플러그인만을 집계합니다.
# plugin-usage.sh 발췌
LOG_DIR="$HOME/.claude/projects/-Users-matsubara"
DAYS="${1:-14}"
...
이 스크립트는 활성화된 플러그인 수와 개별 호출 수 사이의 차이를 '휴면 (Dormant)' 상태로 보고합니다.
TOTAL_ENABLED=$(jq -r '[.enabledPlugins // {} | to_entries[] | select(.value)] | length' "$SETTINGS")
# Dormant = TOTAL_ENABLED - USED_COUNT
grep 대신 jq를 사용하여 집계하는 이유
이전 구현 방식은 부분 문자열 매칭을 위해 grep을 사용했습니다. 이 방식은 JSONL 내의 "지연된 도구 전체 목록 (full list of deferred tools)" 라인(사용 가능한 도구 이름이 나열된 매우 긴 목록)을 "호출 (invocations)"로 잘못 계산했습니다. 이로 인해 terraform과 같이 한 번도 호출되지 않은 도구가 상단에 나타나거나, 심지어 휴면 (Dormant) 카운트가 음수가 되는 문제도 발생했습니다. 모든 데이터를
select(.type=="tool_use")를 통해 통과시킴으로써, 실제로 호출된 호출 (calls) 만이 집계되도록 수정되었습니다.
출력 샘플:
## Summary
- Enabled plugins: **62**
- Distinct plugins referenced in logs: **29**
...
휴면 (Dormant) 상태가 30을 초과하면 "Heavy dormant load"를 출력합니다. 이 임계값은 스크립트 내에 하드코딩되어 있습니다.
2단계 — plugin-auto-disable.sh를 통한 매주 자동 비활성화
~/.claude/scripts/plugin-auto-disable.sh가 핵심입니다. 기본값은 dry (변경 사항 없음)이며, apply를 전달하면 실제로 플러그인을 비활성화합니다.
# plugin-auto-disable.sh 발췌
MODE="${1:-dry}"
DAYS="${DAYS:-30}" # 관찰 기간 (기본 30일)
...
운영 흐름:
- 지난 30일간의 JSONL 데이터에서 MCP 호출(calls) 및 스킬 호출(Skill calls)을 인덱싱합니다.
python3를 통해enabledPlugins에서true로 표시된 플러그인 목록을 가져옵니다.PROTECTED목록에 포함된 항목은 제외합니다.- 후보군을 캐시 크기(cache size) 내림차순으로 정렬하고, 최소
MIN_CACHE_MB이상인 항목 중WEEKLY_MAX개수까지 범위를 좁힙니다. apply모드에서는plugin-disable.sh apply→cleanup-plugin-cache.sh apply를 순차적으로 실행합니다.
# 크기순으로 후보를 정렬하고 WEEKLY_MAX만큼 선택
SIZED=()
for p in "${CANDIDATES[@]}"; do
...
캐시 크기 내림차순으로 우선순위를 두는 이유는 가장 많은 컨텍스트 (context)를 확보할 수 있는 항목부터 먼저 처리하기 위해서입니다.
PROTECTED 목록 설계하기
자동 비활성화의 가장 큰 위험은 오탐 (false positives)입니다. PROTECTED 배열은 "사용 빈도는 낮지만 잃으면 곤란한" 플러그인들을 보호하기 위해 존재합니다.
PROTECTED=(
remember plugin-dev hookify skill-creator session-report
security-guidance superpowers context7 explanatory-output-style
...
각 카테고리가 보호되는 이유:
| 카테고리 | 예시 | 보호 이유 |
|---|---|---|
| 인프라 (Infrastructure) | hookify superpowers remember | 스타트업 훅 (startup hooks) 및 스킬 호출 (skill invocation)의 핵심. JSONL에서 tool_use로 나타나지 않음 |
| ... |
LSP를 PROTECTED에 넣지 않으면 즉시 문제가 발생합니다
typescript-lsp와 그 친구들은 Claude Code 자체에 의해 내부적으로 사용됩니다. 사용자가 스킬 도구 (Skill tool)나 MCP 도구 (MCP tool)로서 명시적으로 호출하지 않기 때문에,tool_use로그에 나타나지 않습니다. 만약 이들이PROTECTED에 포함되어 있지 않다면, 정상적으로 사용 중인 도중에 자동으로 비활성화되어 버립니다.
3단계 — cleanup-plugin-cache.sh를 통한 캐시 아카이브
플러그인을 비활성화한 후에도 해당 캐시는 ~/.claude/plugins/cache/ 아래에 남아 있습니다. cleanup-plugin-cache.sh는 이 캐시들을 ~/.claude/plugins/.disabled-cache/로 이동시킵니다.
# cleanup-plugin-cache.sh 발췌
CACHE_DIR="$HOME/.claude/plugins/cache/claude-plugins-official"
ARCHIVE_DIR="$HOME/.claude/plugins/.disabled-cache"
...
실제로 삭제하지는 않습니다. 단지 mv 명령어를 통해 항목들을 옆으로 옮길 뿐이므로, 단 한 번의 mv 명령으로 복구가 가능합니다.
게다가, purge 서브모드(submode)를 사용하면 .disabled-cache/ 디렉토리에 최소 PURGE_DAYS(기본값 30일) 동안 남아 있는 항목들만 물리적으로 삭제할 수 있습니다.
# 아카이빙 30일 후 물리적으로 삭제
~/.claude/scripts/cleanup-plugin-cache.sh purge
일요일 06:45에 launchd로 자동화하기
저는 ~/Library/LaunchAgents/com.shun.plugin-auto-disable.plist에 주간 스케줄을 설정했습니다.
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
...
매주 일요일 06:45에 plugin-auto-disable.sh apply가 실행되며, 그 로그는 ~/.claude/logs/plugin-auto-disable.log에 추가됩니다.
등록 및 확인:
# 등록
launchctl load ~/Library/LaunchAgents/com.shun.plugin-auto-disable.plist
...
제가 겪었던 함정들 (Pitfalls)
- grep의 부분 일치(substring matching)가 deferred-tools 목록 라인을 "invocations"로 잘못 인식함 →
jq를 사용하여type=="tool_use"를 명시적으로 필터링하기 전까지는 Dormant(휴면) 카운트가 음수로 나타났습니다. terraform 같은 것들이 유령처럼 상단에 나타나기도 했습니다. - LSP를 PROTECTED 목록에 넣는 것을 잊어 비활성화됨 →
typescript-lsp가 제거된 후 타입 체크(type checking)가 작동하지 않았습니다. 이를 PROTECTED에 추가하고 복구했습니다. MIN_CACHE_MB를 설정하지 않으면 캐시가 아주 작은 플러그인들이 대상이 됨 → 5MB 미만의 모든 것은 영향력이 미미하면서 위험만 초래합니다. 기본값을 5MB로 필터링했습니다.WEEKLY_MAX를 너무 높게 설정하면 필요한 플러그인까지 끌어들임 → 주당 5개로 제한했습니다. 서둘러서 한꺼번에 모두 처리할 필요는 없습니다.- Mac이 잠자기 모드일 때 launchd가 작업을 건너뜀 → 일요일 오전 06:45은 기기가 깨어 있는 경우가 많은 시간대입니다. 설령 건너뛰더라도 다음 주로 그냥 넘어갈 뿐이며, 실질적인 해는 없습니다.
plugin-disable.sh가 존재하지 않으면 apply는 아무 작업도 수행하지 않음 (no-op) →plugin-auto-disable.sh는 내부적으로 이 스크립트를 호출하는 래퍼(wrapper)입니다. 전체 스크립트 세트가 갖춰져 있지 않으면 작동하지 않습니다.
요약
plugin-usage.sh는 세션 JSONL 파일에서 실제tool_use이벤트를 집계(aggregate)하고 사용되지 않는 플러그인을 시각화합니다.- 기존의
grep구현 방식은 지연된 도구(deferred-tools) 목록의 라인 수를 잘못 계산합니다. 올바른 해결책은jq를 사용하여type=="tool_use"를 명시적으로 필터링하도록 만드는 것입니다. plugin-auto-disable.sh는 30일간의 비활성 상태, 5MB 이상의 캐시, 그리고 주당 최대 5개라는 제한 조건에 따라 자동으로 비활성화합니다.- LSP, 인프라 플러그인, 그리고 알려진 오탐(false positives) 항목은 항상 PROTECTED(보호) 목록에 포함시켜야 합니다. 그렇지 않으면 필요한 플러그인이 삭제될 수 있습니다.
cleanup-plugin-cache.sh는 캐시를.disabled-cache/로 아카이브하며, 30일 후에purge를 통해 물리적으로 삭제합니다.Weekday=0 / Hour=6 / Minute=45설정이 적용된launchd를 통해 매주 일요일 06:45에 자동으로 실행됩니다.
다음에는 이 과정을 통해 확보한 컨텍스트 여유 공간(context headroom)을 확인하기 위해, 제가 직접 만든 **상태 표시줄(statusline)의 실시간 컨텍스트 사용량 측정기(real-time context-usage meter)**에 대해 작성하겠습니다.
_작성자: Lily — iOS 앱을 출시하며 Claude Code로 콘텐츠 스택을 자동화합니다.
팔로우하기: Portfolio · X · GitHub
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기