이번 시간에는 파일을 캐시해 두고 다시 읽을 때 토큰을 아껴 준다는 MCP 서버, token-optimizer-mcp가 Claude Code에서 실제로 토큰을 줄이는지를 claude -p로 측정하며 배워가는 시간입니다. claude -p는 대화 화면 없이 질문 하나를 보내고 결과를 받는 실행 방식이에요.
자 우리 큰 로그 파일 하나를 질문마다 다시 읽게 하는 과제를 만들어 볼게요. 그리고 내장 Read만 준 조건과 이 서버를 붙인 조건으로 번갈아 보내서, 줄어든 것과 대신 늘어난 것을 같이 보겠습니다.
모델 출력은 비결정적이라 다시 돌리면 숫자가 달라질 수 있어요. --model opus가 가리키는 모델도 시간이 지나면 달라질 수 있습니다.
token-optimizer-mcp가 줄이는 것과 공개 수치
token-optimizer-mcp는 ooples/token-optimizer-mcp 저장소의 MCP 서버예요. npm에서는 @ooples/token-optimizer-mcp로 받습니다.
smart_read, smart_grep 같은 도구가 내장 도구 대신 파일을 읽고 찾으면서, 같은 파일을 다시 읽을 때는 바뀐 부분만 돌려주고 큰 결과는 미리보기로 줄입니다.
서버가 돌려주는 smart_read 도구 설명에는 "-2% to 0% first read, 99-99.9% repeated on an unchanged file (bench/tools, 3 fixtures)"라고 적혀 있어요. 처음 읽을 때는 오히려 조금 더 들고, 바뀌지 않은 파일을 다시 읽을 때 99% 이상 줄어든다는 뜻이에요.
저자가 스스로 밝힌 한계도 있어요. README는 MCP 서버만 붙이는 설치를 "MCP server only (not recommended)"라고 부르고, "adding only the MCP server gives the model tools but no pre-execution veto"라고 적었습니다. 서버만 붙이면 모델에게 도구를 줄 뿐, 내장 Read를 쓰려는 걸 막지는 않는다는 말이에요. 정식 구성은 플러그인, 훅(내장 도구 호출을 미리 막거나 바꾸는 스크립트), 로컬 프록시(모델로 가는 요청을 내 컴퓨터에서 한 번 거쳐 줄이는 서버)까지 함께 까는 방식이고, 이번 글은 저자가 권하지 않는 MCP 서버 단독 구성을 잽니다.
실습 준비
이 글의 명령은 모두 실습 코드의 token-optimizer-mcp-cache/ 디렉터리에서 실행합니다.
gen_log.mjs: 고정 시드로 주문 로그work/app.log와 정답truth.json을 만드는 스크립트make_mcp.sh: 서버를 Docker로 띄우는 MCP 설정mcp.json을 만드는 스크립트lab.sh: 조건별로claude -p를 부르는 셸 함수 모음precheck.sh: 도구를 쓰지 않는 질문으로 서버가 더하는 입력을 확인하는 스크립트prompt.txt: 측정 질문run.sh: 세 조건을 섞어 15번 호출하는 스크립트summarize.mjs: 결과를 조건별로 모으는 스크립트calls.mjs: 호출 하나의 도구 사용을 보여 주는 스크립트README.md: 파일 목록
따라 하시려면 Claude Code 2.1.309(claude --version)와 구독 로그인, Docker가 필요해요. Node와 이 서버는 Mac에 설치하지 않고 node:24-alpine 컨테이너 안에서만 씁니다. 결과 JSON은 jq로 읽어요.
node24는 현재 디렉터리를 컨테이너의 /w로 붙여 Node 스크립트를 실행합니다. 네트워크는 끕니다. 아래 블록은 파일로 만들지 말고 실습 디렉터리에서 연 터미널에 그대로 붙여 넣어 주세요. 이후 명령은 모두 이 터미널에서 실행하고, 터미널을 새로 열면 다시 붙여 넣으시면 됩니다.
node24() { docker run --rm --network none -v "$PWD":/w -w /w node:24-alpine node "$@"; }
먼저 로그 파일을 만들게요. gen_log.mjs는 시간, 주문 번호, 사용자, 금액, 상태가 한 줄씩 적힌 주문 로그 700줄을 만들고, 질문 세 개의 정답을 truth.json에 따로 적어요. 정답 파일은 claude -p를 실행할 work/ 밖에 두어서 Claude가 작업 디렉터리에서 볼 일이 없게 했습니다.
node24 gen_log.mjs
sed -n '1p;412p;655p;700p' work/app.log
work/app.log: 700줄, 55368바이트
{"q1":{"order":"O-52026","amount":61000},"q2":{"order":"O-53309","user":"u-2957"},"q3":{"order":"O-53532"}}
2026-10-01T09:00:03Z INFO order=O-50008 user=u-9527 amount=8700 status=shipped
2026-10-01T10:17:38Z INFO order=O-52026 user=u-4092 amount=61000 status=shipped
2026-10-01T10:58:16Z INFO order=O-53309 user=u-2957 amount=47700 status=paid
2026-10-01T11:07:24Z INFO order=O-53532 user=u-2463 amount=146000 status=refunded
정답이 든 줄은 412번째, 655번째, 700번째 줄이에요. 파일 앞부분만 보고는 답할 수 없게 일부러 뒤쪽에 두었습니다.
이제 MCP 설정을 만들게요. make_mcp.sh는 서버 이름을 tom으로 정하고, Claude Code가 서버를 띄울 때 docker run -i --rm으로 node:24-alpine 컨테이너를 하나 만들어 npx -y @ooples/token-optimizer-mcp@7.6.0을 실행하게 하는 mcp.json을 만들어요. -v "$PWD/work:$PWD/work:ro"는 work/를 컨테이너 안에도 같은 절대 경로로, 읽기 전용으로 붙여요. Claude는 Mac의 경로로 파일을 가리키니, 컨테이너 안에서도 같은 경로가 있어야 서버가 파일을 찾아요.
zip에서 푼 스크립트는 실행 권한이 없어서 먼저 chmod +x로 붙여 줍니다.
chmod +x make_mcp.sh precheck.sh run.sh
./make_mcp.sh
mcp.json을 만들었습니다
여기서 하나 기억해 두실 게 있어요. Claude Code는 claude -p를 부를 때마다 이 컨테이너를 새로 띄우고, 끝나면 --rm으로 지웁니다. 그래서 서버의 캐시는 호출 한 번 안에서만 살아 있어요. 다음 호출은 빈 캐시로 시작합니다.
실습 1: 도구 19개가 매 요청에 더하는 입력
모든 호출은 lab.sh의 셸 함수 ask로 부릅니다(발췌).
ask() {
local cond="$1" prompt="$2" out="$3"
local tools=(--tools Read)
case "$cond" in
mcp) tools=(--tools Read --mcp-config ../mcp.json) ;;
mcp-only) tools=(--tools "" --mcp-config ../mcp.json) ;;
esac
(cd work && MCP_TIMEOUT=120000 perl -e 'alarm shift; exec @ARGV' 900 \
claude -p "$prompt" "${tools[@]}" \
--model opus --effort medium \
--setting-sources project --strict-mcp-config \
--allowedTools "Read mcp__tom" \
--no-session-persistence --output-format stream-json --verbose \
< /dev/null) > "$out" 2> "${out%.jsonl}.err"
}
ask는 work/로 들어가 조건에 맞는 도구만 주고 claude -p를 부릅니다. 조건은 세 가지예요.
base: 내장 Read만 줍니다. MCP 서버는 없어요.mcp: 내장 Read와 token-optimizer-mcp를 함께 줍니다. 어느 쪽을 쓸지는 Claude가 고릅니다.mcp-only: 내장 도구를 모두 빼고 token-optimizer-mcp만 줍니다. 파일을 읽으려면 서버 도구를 쓸 수밖에 없어요.
나머지 플래그는 모든 조건에서 같아요.
--setting-sources project,--strict-mcp-config: 내 사용자 설정, 사용자 CLAUDE.md, 계정에 연결된 MCP 서버가 섞이지 않게 합니다. MCP 서버는--mcp-config로 준 것만 붙어요.--output-format stream-json --verbose: 최종 결과만이 아니라 Claude가 부른 도구와 도구 결과를 한 줄씩 모두 남깁니다.
--tools에 ToolSearch(도구 정의를 필요할 때 찾아 싣는 내장 도구)를 넣지 않았으니, 서버의 도구 19개는 처음부터 모두 요청에 실립니다. tool search가 더 궁금하시면 tool search를 잰 글을 참고해 주세요.
precheck.sh는 도구를 쓸 일이 없는 질문 "도구를 쓰지 말고 OK라고만 답해 주세요."를 base와 mcp에 한 번씩 보내요.
./precheck.sh
jq -c 'select(.subtype == "init") | {model, tools, mcp_servers}' out/precheck/base.jsonl out/precheck/mcp.jsonl
jq -c 'select(.type == "result") | {result, total_cost_usd, usage: (.usage | {input_tokens, cache_creation_input_tokens, cache_read_input_tokens, output_tokens})}' out/precheck/base.jsonl out/precheck/mcp.jsonl
precheck base
precheck mcp
{"model":"claude-opus-5-5","tools":["Read"],"mcp_servers":[]}
{"model":"claude-opus-5-5","tools":["Read","mcp__tom__cache_audit","mcp__tom__expand","mcp__tom__fleet_audit","mcp__tom__get_cached","mcp__tom__get_optimization_report","mcp__tom__install_doctor","mcp__tom__model_routing","mcp__tom__optimize_session","mcp__tom__optimize_text","mcp__tom__smart_edit","mcp__tom__smart_glob","mcp__tom__smart_grep","mcp__tom__smart_read","mcp__tom__smart_write","mcp__tom__token_audit","mcp__tom__waste_audit","mcp__tom__wiki_query","mcp__tom__wiki_read","mcp__tom__wiki_write"],"mcp_servers":[{"name":"tom","status":"connected","source":"dynamic"}]}
{"result":"OK","total_cost_usd":0.01915,"usage":{"input_tokens":2,"cache_creation_input_tokens":2341,"cache_read_input_tokens":1670,"output_tokens":4}}
{"result":"OK","total_cost_usd":0.11468,"usage":{"input_tokens":2,"cache_creation_input_tokens":14324,"cache_read_input_tokens":0,"output_tokens":4}}
위 두 줄은 시작 이벤트(init)에서 고른 필드예요. mcp에서는 서버가 connected로 붙었고, Read 옆에 mcp__tom__으로 시작하는 도구 19개가 생겼어요.
아래 두 줄은 결과 이벤트의 발췌입니다. 입력 세 필드를 더하면 base가 4,013토큰, mcp가 14,326토큰이에요. 똑같이 "OK"라고만 답했는데 10,313토큰이 더 실렸고, 이게 도구 정의 19개와 서버가 모델에게 주는 안내문(instructions)의 몫입니다.
안내문은 "Token optimization is mandatory routing policy, not a preference: use smart_read for large or repeated files"로 시작해요. 서버가 모델에게 자기 도구를 쓰라고 꽤 강하게 말하는 거죠.
이 10,313토큰은 한 번만 내는 게 아니에요. 도구를 한 번 부를 때마다 Claude Code는 모델에게 요청을 다시 보내고, 그때마다 도구 정의도 다시 실립니다.
실습 2: 같은 파일을 다시 읽는 질문을 세 조건으로 보내기
이 서버가 가장 크게 줄인다고 말하는 경우는 바뀌지 않은 파일을 다시 읽을 때라서, 질문마다 파일을 다시 읽으라고 한 질문을 만들었어요.
cat prompt.txt
현재 디렉터리의 app.log는 주문 처리 로그입니다. 아래 세 질문에 순서대로 답해 주세요. 질문마다 답하기 전에 app.log를 다시 읽어서 확인해 주세요. 앞에서 읽은 내용을 기억으로 쓰지 말고 매번 파일을 새로 읽어야 합니다.
1. order=O-52026 줄의 amount 값은 얼마인가요?
2. order=O-53309 줄의 user 값은 무엇인가요?
3. 파일 맨 마지막 줄의 order 값은 무엇인가요?
답의 맨 마지막 줄에는 {"q1": 숫자, "q2": "문자열", "q3": "문자열"} 형식의 JSON 한 줄만 적어 주세요.
run.sh는 같은 질문을 세 조건으로 5회차에 걸쳐 보내고, 호출마다 기록을 out/<조건>/run-<회차>.jsonl에 남겨요. 한 조건을 몰아서 돌리지 않도록 회차마다 순서를 바꾸고, 마지막 줄이 성공한 결과 이벤트인 회차는 건너뜁니다. 한도 오류로 멈췄다면 같은 명령을 다시 돌려 남은 호출만 이어 가면 돼요.
실행해 볼게요. 15번 호출하니 몇 분 걸립니다.
./run.sh
round 1 base
round 1 mcp
round 1 mcp-only
round 2 mcp
round 2 mcp-only
round 2 base
round 3 mcp-only
round 3 base
round 3 mcp
round 4 base
round 4 mcp-only
round 4 mcp
round 5 mcp
round 5 base
round 5 mcp-only
summarize.mjs는 조건마다 기록 5개를 읽어 호출별로 정답 여부, 부른 도구의 순서, 입력 토큰(세 필드의 합)과 그중 캐시에 새로 쓴 토큰, 출력 토큰, 비용 추정, 턴 수, 시간을 찍고, 마지막에 중앙값을 냅니다. 정답은 답의 마지막 JSON에서 q1, q2, q3을 꺼내 truth.json과 그대로 비교해요.
node24 summarize.mjs
base: 5회, 정답 5/5
run-1.jsonl: 정답 O, 도구 Read Read Read Read Read, 입력 203,875 (캐시 쓰기 65,302), 출력 1,447, $0.5791, 6턴, 22.0초
run-2.jsonl: 정답 O, 도구 Read Read Read, 입력 104,692 (캐시 쓰기 32,881), 출력 1,218, $0.3018, 4턴, 19.0초
run-3.jsonl: 정답 O, 도구 Read Read Read, 입력 104,176 (캐시 쓰기 9,884), 출력 1,169, $0.1213, 4턴, 15.6초
run-4.jsonl: 정답 O, 도구 Read Read Read, 입력 104,242 (캐시 쓰기 9,931), 출력 1,112, $0.1206, 4턴, 20.6초
run-5.jsonl: 정답 O, 도구 Read Read Read, 입력 104,167 (캐시 쓰기 9,881), 출력 1,255, $0.1230, 4턴, 17.0초
중앙값: 입력 104,242, 출력 1,218, $0.1230, 4턴, 19.0초
mcp: 5회, 정답 5/5
run-1.jsonl: 정답 O, 도구 Read smart_grep Read Read, 입력 185,333 (캐시 쓰기 34,655), 출력 1,794, $0.3433, 5턴, 20.9초
run-2.jsonl: 정답 O, 도구 Read smart_grep smart_grep Read, 입력 166,852 (캐시 쓰기 24,256), 출력 1,290, $0.2484, 5턴, 19.3초
run-3.jsonl: 정답 O, 도구 smart_read smart_grep smart_grep Read, 입력 87,787 (캐시 쓰기 4,528), 출력 1,466, $0.0822, 5턴, 19.9초
run-4.jsonl: 정답 O, 도구 Read smart_grep smart_grep Read, 입력 167,267 (캐시 쓰기 24,530), 출력 1,354, $0.2519, 5턴, 22.6초
run-5.jsonl: 정답 O, 도구 Read Read smart_grep Read, 입력 193,890 (캐시 쓰기 33,285), 출력 1,568, $0.3298, 5턴, 18.9초
중앙값: 입력 167,267, 출력 1,466, $0.2519, 5턴, 19.9초
mcp-only: 5회, 정답 5/5
run-1.jsonl: 정답 O, 도구 smart_read smart_grep smart_grep smart_grep, 입력 85,055 (캐시 쓰기 19,306), 출력 1,679, $0.2012, 5턴, 22.9초
run-2.jsonl: 정답 O, 도구 smart_read smart_grep smart_grep smart_grep, 입력 84,905 (캐시 쓰기 5,279), 출력 1,643, $0.0911, 5턴, 18.1초
run-3.jsonl: 정답 O, 도구 smart_read expand smart_grep smart_grep smart_grep smart_grep, 입력 105,442 (캐시 쓰기 6,267), 출력 1,987, $0.1098, 7턴, 27.6초
run-4.jsonl: 정답 O, 도구 smart_read expand smart_grep smart_grep smart_grep, 입력 105,881 (캐시 쓰기 6,455), 출력 2,024, $0.1121, 6턴, 23.7초
run-5.jsonl: 정답 O, 도구 smart_read smart_grep smart_grep smart_grep smart_grep, 입력 103,752 (캐시 쓰기 5,536), 출력 2,134, $0.1067, 6턴, 21.8초
중앙값: 입력 103,752, 출력 1,987, $0.1098, 6턴, 22.9초
정답은 세 조건 모두 5번 중 5번이었어요. base의 1회차만 Read를 다섯 번 불러 질문마다 파일을 정말 다시 읽었고, 2~5회차는 Read 세 번으로 끝났어요. 그래서 비교는 중앙값으로 할게요.
중앙값으로 보면 mcp는 입력이 6만 3천 토큰쯤(1.6배) 많고 비용 추정은 2배예요. 그런데 도구 칸을 보면 smart_read를 부른 건 5번 중 3회차 한 번뿐이에요. 나머지 4번은 서버 안내문이 "mandatory"라고 했는데도 내장 Read로 파일을 읽고 smart_grep으로 주문 줄을 찾았습니다.
smart_grep으로 주문 줄을 찾은 만큼은 줄였지만, 도구 정의 1만 토큰을 턴마다 다시 싣고 턴도 하나 늘어서, 줄인 것보다 더한 것이 훨씬 컸습니다.
다만 비용 2배는 조심해서 봐 주세요. 같은 질문을 몇 분 간격으로 되풀이하다 보니, 앞 호출이 캐시에 써 둔 내용을 다음 호출이 그대로 읽은 경우가 있었어요. 캐시에 새로 쓴 토큰이 많았던 base 2회차는 $0.3018로, mcp 2·4회차의 $0.2484, $0.2519보다 비쌌습니다. 그래서 조건 비교는 캐시 상태와 상관없이 같은 값이 나오는 입력 토큰 합(1.6배)으로 보시는 게 안전해요.
mcp-only는 입력이 base와 거의 같고 비용 추정은 $0.1098로 조금 낮아요. 하지만 출력 토큰 중앙값은 1,987로 base의 1,218보다 많고 턴도 두 개 더 썼어요.
실습 3: smart_read를 실제로 불렀는지 확인하기
calls.mjs는 stream-json 기록에서 도구 호출(tool_use)과 도구 결과(tool_result)를 짝지어, Claude가 부른 도구를 순서대로 인자와 함께 찍고 그 결과가 몇 글자였는지 붙여요. 내장 Read라면 몇 번째 줄부터 몇 줄을 받았는지도 같이 찍습니다. 두 번째 인자로 번호를 주면 그 번째 도구 결과의 본문을 홈 디렉터리 아래 경로를 가려 보여 줘요.
mcp에서 smart_read를 부른 유일한 3회차와, mcp-only의 4회차를 볼게요.
node24 calls.mjs out/mcp/run-3.jsonl
node24 calls.mjs out/mcp-only/run-4.jsonl
1. smart_read {"path":"app.log","enableCache":false,"diffMode":false} -> 4,840자
2. smart_grep {"pattern":"order=O-52026 ","path":"work","files":["app.log"]} -> 504자
3. smart_grep {"pattern":"order=O-53309 ","path":"work","files":["app.log"]} -> 502자
4. Read {"file_path":"app.log","offset":695} -> 507자, 695줄부터 7줄
1. smart_read {"path":"app.log","enableCache":false,"diffMode":false} -> 4,840자
2. expand {"ref":"452b1916fc969576","section":"routine log lines"} -> 1,514자
3. smart_grep {"pattern":"order=O-52026 ","path":"work","files":["app.log"],"useCache":false,"caseSensitive":true} -> 504자
4. smart_grep {"pattern":"order=O-53309 ","path":"work","files":["app.log"],"useCache":false,"caseSensitive":true} -> 501자
5. smart_grep {"pattern":".","regex":true,"path":"work","files":["app.log"],"useCache":false,"offset":690,"limit":100} -> 1,770자
여기서 눈여겨볼 게 세 가지 있어요.
첫째, Claude가 smart_read를 부르면서 enableCache: false, diffMode: false를 직접 넣었어요. 질문에 "앞에서 읽은 내용을 기억으로 쓰지 말고"라고 적은 걸 캐시를 끄라는 뜻으로 받아들인 것으로 보여요. 이 서버가 99%를 줄인다는 바로 그 기능을 모델이 스스로 끈 거죠. 그러니 위 15번에서 smart_read를 부른 6번은 다시 읽기 캐시를 처음부터 쓰지 않은 채로 잰 거예요.
둘째, smart_read의 첫 결과는 파일 전체가 아니라 4,840자였어요. 결과 끝부분을 볼게요.
node24 calls.mjs out/mcp-only/run-4.jsonl 1 | tail -n 2
2026-10-01T09:10:48Z INFO order=O-50307 user=u-3573 amount=48700 status=paid
---- omitted: 641 lines of content > routine log lines (expand 452b1916fc969576) ----
앞 60줄만 보여 주고 나머지 640줄(미리보기 표시로는 641줄)은 "routine log lines"로 접었어요. 정답이 든 412번째, 655번째, 700번째 줄은 모두 이 접힌 부분에 있습니다. 미리보기만으로는 세 질문 중 어느 것도 답할 수 없었던 거예요.
셋째, 접힌 부분을 펼치는 expand도 소용이 없었어요. 결과 첫 줄을 볼게요.
node24 calls.mjs out/mcp-only/run-4.jsonl 2 | head -n 1
Error: result (50,613 characters across 640 lines) exceeds maximum allowed tokens. Output has been saved to ~/.claude/projects/<작업 디렉터리>/<세션>/tool-results/mcp-tom-expand-1792758735977.txt.
펼친 결과가 50,613자라 Claude Code의 MCP 결과 한도를 넘었고, Claude Code는 결과를 모델에게 주지 않고 파일로 뺐어요. 이 한도가 더 궁금하시면 MCP 도구가 큰 결과를 돌려줄 때를 잰 글을 참고해 주세요. mcp-only에는 그 파일을 읽을 Read가 없으니, Claude는 결국 smart_grep으로 필요한 줄을 하나씩 찾아 답했습니다.
summarize.mjs 출력의 도구 칸을 보면, 서버를 붙인 어느 호출에서도 smart_read는 처음 한 번만 불렸어요. 같은 파일을 두 번째로 읽은 적은 한 번도 없었습니다.
그럼 99%는 틀린 수치일까요? 그렇게 말할 수는 없어요. 다만 Claude Code가 그 경로로 들어가게 만들지 못했다는 게 우리 측정의 결과예요. 내장 도구를 모두 뺀 mcp-only에서도 다시 읽기는 없었으니, 권장 구성에서 99% 경로가 실행될지는 이번 측정으로 알 수 없습니다.
정리
범위: Claude Code 2.1.309의 claude -p, Opus 5.5(claude-opus-5-5), effort medium, @ooples/token-optimizer-mcp 7.6.0을 Docker로 띄운 MCP 서버 단독 구성(훅·프록시 없음), 주문 로그 700줄 하나에 질문 세 개, 도구 확인 2번 + 세 조건 5번씩 15번 호출.
- 서버를 붙이기만 해도 도구 19개와 안내문이 요청마다 입력을 10,313토큰 늘렸습니다. 도구를 부를 때마다 다시 실리니, 서버를 붙이기 전에 이 고정 비용부터 확인하세요.
- "기억으로 쓰지 말라"는 문장이 든 질문에서 내장 Read와 함께 주자 Claude는 5번 중 1번만
smart_read를 불렀고, 입력 토큰 중앙값은 104,242에서 167,267로 1.6배가 됐습니다. 서버 안내문이 "mandatory"라고 해도 모델이 내장 도구를 고를 수 있어요. - 서버를 붙인 호출에서
smart_read가 같은 파일을 다시 읽은 적은 없어서, 다시 읽기 99% 감소는 확인하지 못했습니다. "기억으로 쓰지 말라"는 문장이 든 질문에서는 smart_read 6번 모두 Claude가enableCache: false를 넣었어요. 이 글에 싣지 않은 추가 측정에서 그 문장을 뺀 질문을mcp-only로 3번 보냈을 때는 넣지 않았지만 그때도smart_read는 처음 한 번만 불렸습니다. - Claude Code 세션 안에서는
smart_read의 첫 결과가 15번 중 부른 6번 모두 앞 60줄만 보여 주고 640줄을 접은 4,840자 미리보기였고, 정답이 든 줄은 모두 접힌 쪽에 있었습니다.expand로 펼친 50,613자는 Claude Code의 MCP 결과 한도를 넘어mcp-only3·4회차 2번 모두 파일로 빠졌어요. - 저자가 권하는 플러그인·훅·프록시 구성과, 여러 턴을 이어 가는 대화형 세션의 효과는 재지 않았습니다.