claude 대신 claude-tap이라고 치기만 하면 된다.
liaohch3/claude-tap · 언어 Python 3.11+ · 버전 v0.1.139 · claude_tap 패키지 ~30개 모듈·16,000줄+ · 지원 클라이언트 16종 · 의존성 단 3개 · ★ 2.7k · 라이선스 MIT · 원저자 liaohch3 · shallow clone 소스 직접 분석)
요즘 우리는 터미널에서 claude, codex, gemini 같은 명령을 치고 AI에게 코드를 맡긴다. 그런데 그 도구들은 안에서 무슨 일이 벌어지는지 거의 보여주지 않는다. 에이전트가 이상한 답을 하거나, 토큰을 갑자기 폭발적으로 쓰거나, 엉뚱한 파일을 건드릴 때 — 우리는 그저 결과만 보고 "왜 그랬을까"를 추측한다. 정작 모델에게 실제로 무엇이 전달됐는지는 볼 수 없다.
코딩 에이전트를 쓰는 건 "환자의 겉모습만 보고 진단하는 것"과 같다 — 답이 이상해도 원인을 몸 밖에서 추측할 뿐이다. claude-tap은 "몸속을 찍는 영상 장비"다. 매 요청마다 모델에 실린 시스템 프롬프트, 대화 이력, 툴 목록, 파라미터를 원본 그대로 펼쳐 보여준다. 게다가 이 "영상"은 전부 내 컴퓨터 안에만 저장된다.
claude-tap이 하는 일은 명확하다. 에이전트와 API 서버 사이에 끼어 앉아(프록시) → 오가는 요청·응답을 그대로 복사해 기록하고 → 스트리밍 응답을 온전한 메시지로 재조립한 뒤 → 자기완결형 HTML 뷰어(self-contained viewer)로 펼쳐 보여준다. 핵심은 "가로채되 바꾸지는 않는다"는 것 — 통신을 있는 그대로 흘려보내면서 사본만 남긴다.
이름은 claude-tap이지만 Claude 전용이 아니다. Claude Code, Codex CLI, Codex App, Gemini CLI, Grok, Kimi, OpenCode, Cursor CLI, Qoder, CodeBuddy 등 16종을 지원한다. 여기서 tap은 전화 "도청(wiretap)"의 그 tap이다 — 선에 살짝 손대어(tap) 오가는 신호를 엿듣는다는 뜻.
2026년, 코딩 에이전트는 넘쳐난다. 그런데 정작 "이 에이전트가 내부적으로 어떤 프롬프트를 쓰는지", "매 턴 컨텍스트를 어떻게 쌓는지"는 다들 궁금해하면서도 알 방법이 없었다. claude-tap이 트렌딩에 오른 이유는 이 답답함을 정확히 긁어주기 때문이다 — "에이전트의 속을 들여다보고 싶다"는 개발자들의 공통된 욕구를 하나의 도구로 해결했다.
비슷한 도구(예: mitmproxy, Charles Proxy)는 범용 HTTP 프록시라, 코딩 에이전트마다 인증 방식·엔드포인트·스트리밍 포맷이 제각각인 걸 일일이 사람이 맞춰줘야 한다. claude-tap은 이 지저분한 배선 작업을 클라이언트별로 미리 내장했다. --tap-client codex처럼 이름만 대면, 그 CLI의 인증 상태를 읽어 알맞은 상류(upstream)와 프록시 모드를 자동으로 골라준다.
| 지원 클라이언트(16종) | 대표 상류 / 특징 |
|---|---|
| Claude Code | Anthropic API · AWS Bedrock · Vertex · DeepSeek/GLM 게이트웨이 자동 감지 |
| Codex CLI / Codex App | OpenAI API 키 또는 ChatGPT 구독 OAuth · 앱은 백엔드 WebSocket까지 포착 |
| Gemini CLI | Google OAuth / Code Assist (포워드 프록시 기본) |
| Grok / Kimi / Kimi Code | 공식 CLI 프록시·네이티브 base URL 환경변수 활용 |
| OpenCode / MiMo / Pi / Hermes / OpenClaw | 멀티 프로바이더 — 어떤 상류든 잡으려고 포워드 모드 기본 |
| Cursor CLI / Qoder / Antigravity / CodeBuddy | base URL 오버라이드가 없어 포워드 프록시 + 로컬 트랜스크립트 병행 |
여기가 기술적으로 가장 영리한 부분이다. ChatGPT 구독 OAuth나 AWS SigV4 서명처럼 인증이 걸린 통신은 프록시가 URL을 함부로 바꾸면 서명이 깨져 인증이 실패한다. claude-tap은 이럴 때 포워드 프록시(forward proxy) 모드로 전환해, 클라이언트가 진짜 서버 주소를 그대로 믿게 두고 중간에서 TLS만 살짝 벗겨 훔쳐본다. 덕분에 OAuth 세션을 건드리지 않는다.
트레이스는 전부 로컬 SQLite에 저장되고 호스팅 대시보드가 필요 없다. 기록 전에 Authorization·Cookie·x-api-key 같은 민감 헤더는 자동으로 가려진다(redaction). 게다가 스캐너가 /etc/passwd 같은 엉뚱한 경로로 찔러도, 알려진 API 경로가 아니면 상류로 보내지도 기록하지도 않고 404로 막는다.
속을 보고 싶은 호기심(프롬프트 훔쳐보기), 디버깅의 실용성(왜 이 답이 나왔나), 자료를 밖으로 안 보내는 안전함(로컬 저장·헤더 마스킹) — 이 셋을 설치 명령 한 줄과 실행 명령 한 단어로 묶었다. 진입 장벽이 거의 없다는 게 트렌딩의 진짜 동력이다.
claude-tap의 기술 선택에는 뚜렷한 철학이 있다: "가볍고, 설치가 쉽고, 어디서나 돌아간다". 네트워크 도구인데도 무거운 프레임워크가 하나도 없다. 전부 Python 비동기 표준 위에 얹혀 있다.
| 레이어 | 기술 | 역할 |
|---|---|---|
| 런타임 | Python 3.11+ (개발 3.13) | 전 계층이 asyncio 기반 단일 프로세스 비동기 |
| HTTP/WS | aiohttp 3.14 | 프록시 서버·상류 클라이언트·SSE/WebSocket 중계·뷰어 웹서버까지 모두 담당 |
| 암호화 | cryptography ≥42 | 포워드 프록시용 CA·호스트 인증서를 코드로 생성(MITM TLS) |
| 압축 | backports-zstd | zstd 응답 해제(3.14 미만 파이썬 보완) · gzip/zlib은 표준 |
| 저장 | SQLite (sqlite3 표준) | 트레이스를 traces.sqlite3 단일 DB에 기록(스키마 v4) |
| 패키징 | setuptools-scm · uv/pip | git 태그에서 버전 자동 산출 · uv tool install 권장 |
보통 이런 도구는 "프레임워크 뷔페"를 차린다 — 웹서버 하나, HTTP 클라이언트 하나, 암호화 라이브러리 하나, 각각 다른 벤더. claude-tap은 aiohttp라는 만능 칼 하나로 서버·클라이언트·스트리밍·웹뷰어를 전부 해낸다. 의존성이 3개뿐이라 pip install 한 번이면 끝나고, 충돌날 부품 자체가 적다.
뷰어는 React도 번들러도 쓰지 않는다. viewer.html과 dashboard.html에 바닐라 JS·CSS를 직접 넣고, 파이썬이 트레이스 데이터를 주입해 단일 HTML 파일 하나로 뽑아낸다. 그래서 결과물을 이메일로 보내거나 CI 아티팩트로 올려도 어디서든 브라우저만 있으면 열린다.
| 뷰어 구성 | 내용 |
|---|---|
| viewer.html / .py | 내보내기용 정적 뷰어 — 구조적 diff·검색·툴 인스펙터·토큰 분석·다크모드 |
| dashboard.html | 저장된 여러 세션을 브라우징하는 대시보드(클라이언트 실행 없이도 열람) |
| viewer_i18n.json | 8개 언어(영어·简体中文·日本語·한국어·Français·العربية·Deutsch·Русский) |
| 라이브 모드 | live.py가 SSE로 브라우저에 실시간 push — 실행 중 새 요청이 즉시 뜸 |
순수 CLI 도구를 넘어 OS별 통합까지 챙긴다. macOS에서는 macos_app.py·macos_bundle.py가 메뉴바 앱을 빌드해준다(claude-tap build-macos-app). Apple Silicon용으로 파이썬까지 통째로 넣은 self-contained 번들도 만들 수 있어, 파이썬이 없는 동료에게도 .app 하나만 건네면 된다.
claude-tap의 모든 것은 "어떻게 통신 중간에 끼어들 것인가"로 귀결된다. 그런데 클라이언트마다 사정이 다르다. 어떤 CLI는 "서버 주소를 바꿔줄게"라는 환경변수를 순순히 받아들이고, 어떤 CLI는 주소를 절대 못 바꾸게 막혀 있다. 그래서 두 가지 프록시 모드를 준비했다.
base URL 환경변수(ANTHROPIC_BASE_URL 등)를 지원하는 클라이언트에 쓴다. 에이전트에게 "진짜 서버 대신 내 로컬 주소를 보라"고 알려주는 방식이다. 가장 깔끔하다 — 요청 하나당 트레이스 하나가 온전히 남는다.
포인트는 strip_path_prefix 같은 경로 정규화다. 예컨대 Codex OAuth는 /v1/*로 요청을 보내지만 상류는 이미 /codex 백엔드라 /v1을 떼줘야 한다. 이런 클라이언트별 규칙이 ClientConfig에 선언돼 있다.
주소를 못 바꾸는(멀티 프로바이더거나 OAuth/서명이 걸린) 클라이언트에 쓴다. 이때는 HTTPS_PROXY 환경변수와 우리 CA 인증서를 자식 프로세스에 심어, 클라이언트가 진짜 서버 주소를 그대로 믿으면서 실제로는 우리 프록시를 거치게 한다.
cryptography로 CA를 직접 굽는 이유다.
첫 실행 때 ~/.claude-tap/에 자체 서명 CA(ca.pem + ca-key.pem, 유효기간 5년, RSA-2048)를 한 번 굽는다. 이후 접속하는 호스트마다 이 CA로 서명한 인증서를 즉석에서 만들어(1년 유효) 메모리에 캐시한다. macOS에서 환경변수 CA를 무시하는 일부 앱(예: Antigravity)은 시스템 키체인이 아니라 사용자 로그인 키체인에만 CA를 신뢰 등록한다 — sudo 없이, 최소 권한으로.
① 클라이언트별 설정을 데이터로 (ClientConfig dataclass): "코덱스는 이렇게, 제미나이는 저렇게" 같은 분기를 코드가 아니라 선언적 데이터로 표현했다. base_url_env, default_proxy_mode, strip_path_prefix, auto_trust_ca_macos 같은 필드만 채우면 새 클라이언트가 추가된다. 16종을 감당하는 비결.
② "가로채되 바꾸지 않는다": 상류로 보내는 원본 바이트는 절대 훼손하지 않고, 트레이스용 사본만 파싱한다(_parse_request_body_for_trace). 그래서 프록시가 끼어도 에이전트 동작이 달라지지 않는다.
③ 포맷 차이를 한 곳에서 흡수: Anthropic·OpenAI Responses·OpenAI Chat Completions·AWS Bedrock EventStream까지 응답 포맷이 다 다른데, SSEReassembler와 usage.normalize_usage가 "내부 표준 형태" 하나로 정규화한다. 뷰어는 포맷을 신경 쓸 필요가 없다.
구조가 말해주는 이야기는 분명하다. "가로채기(proxy)"가 가장 두껍고(코어), "설정(cli_clients)"이 그 다음이다. 16종 클라이언트의 제각각인 인증·엔드포인트를 흡수하는 cli_clients.py(2,125줄)가 단일 최대 모듈이라는 게, 이 프로젝트의 진짜 난이도가 "프록시 그 자체"보다 "현실의 도구들을 다 맞춰주는 것"에 있음을 보여준다.
공항의 보안 검색대를 떠올리면 된다. 실제로 짐을 통과시키는 X-ray 기계(proxy)는 한 대지만, 나라마다 다른 여권·비자·언어를 처리하는 입국 심사 매뉴얼(cli_clients)이 훨씬 두껍다. claude-tap도 "찍는 장치"는 단순하고, "온갖 여행객을 응대하는 규칙"이 방대하다.
"HTTPS는 암호화돼서 못 본다"는 통념을 forward_proxy.py + certs.py가 실물로 깨준다. CONNECT 터널링, TLS 종료, CA로 호스트 인증서를 즉석 발급하는 과정을 200줄 남짓한 읽을 만한 코드로 볼 수 있다. Charles·mitmproxy가 안에서 뭘 하는지 궁금했다면 최고의 교재다.
AI 응답은 SSE 스트림으로 온다. 이걸 "다 받고 나서" 처리하면 라이브 뷰어가 불가능하다. claude-tap은 조각이 도착하는 즉시 클라이언트로 흘려보내면서 동시에 사본을 재조립한다(sse.py의 feed_bytes). 지연을 안 만들면서 가로채는 비동기 패턴의 좋은 예다.
16종을 if client == ... 분기로 짰다면 코드가 지옥이 됐을 것이다. ClientConfig dataclass 하나로 새 클라이언트 = 새 데이터 한 줄이 되도록 만든 설계는, 어떤 프로젝트에서든 재사용할 수 있는 확장 지점(extension point) 패턴이다.
코드 밖도 배울 게 많다. docs/support-matrix.md는 (클라이언트 × 인증 × 상류 × 전송)의 검증된 조합을 표로 관리하고, "프록시/라우팅 변경 시 해당 행을 전부 재검증"을 규칙으로 못박았다. 커버리지 게이트(신규 코드 80%), 이중 언어 문서, real E2E 증거(가짜 스크린샷 금지) 같은 성숙한 프로젝트 운영을 통째로 관찰할 수 있다.
claude-tap은 내 기기에서 내 트래픽을 보는 도구다. 하지만 원리상 로컬에 신뢰된 CA를 설치한다는 점은 이해하고 있어야 한다. 이 CA 개인키(~/.claude-tap/ca-key.pem)가 유출되면 남이 내 기기의 HTTPS를 위조할 수 있으니, 키 파일을 공유하거나 복사해 옮기지 말 것. 남의 기기·회사 자산에 무단으로 트래픽 가로채기를 적용하는 건 당연히 금물이다.
claude-tap으로 Claude Code를 한 번 돌리고, 생성된 HTML 뷰어에서 시스템 프롬프트 전문을 읽어보기 — "숨겨진 지시"가 얼마나 긴지 확인--tap-store-stream-events를 켜고 SSE 이벤트 원본 배열을 열어, 재조립 전/후를 비교| 항목 | 요구사항 |
|---|---|
| Python | 3.11 이상(3.11 / 3.12 / 3.13 분류자 명시, 개발 3.13) |
| 설치 | uv tool install claude-tap(권장) 또는 pip install claude-tap |
| 외부 의존성 | 단 3개 — aiohttp · cryptography · backports-zstd |
| OS | macOS · Linux · Windows(윈도우는 claude-tap.exe 경로 이슈 안내 있음) |
| 대상 클라이언트 | 추적하려는 CLI(claude/codex/gemini …)는 별도로 설치·로그인돼 있어야 함 |
| 저장 공간 | 트레이스는 ./.traces에 SQLite로 · 기본 최근 50세션 유지(--tap-max-traces로 조절) |
| 네트워크 | 기본 127.0.0.1 바인딩(로컬 전용) · 프록시-온리 모드에선 0.0.0.0 |
| macOS 앱 | 메뉴바 앱 빌드 시 · self-contained 번들은 Apple Silicon(PyInstaller) |
요구사항이 이렇게 얇은 건 의도된 설계다. 디버깅 도구는 "쓰고 싶은 순간 바로 써져야" 가치가 있다 — 무거운 셋업이 필요하면 정작 급할 때 안 쓰게 된다. claude를 claude-tap으로 바꿔 치는 마찰 없는 진입이 이 도구의 철학이다.
uv tool install claude-tap 후 claude-tap으로 평소처럼 한 번 대화한다. 종료하면 뜨는 HTML 뷰어에서 첫 요청을 열어 system 프롬프트와 tool 정의 목록을 읽어본다. "에이전트가 실제로 뭘 아는지"를 처음으로 눈으로 확인하는 경험. 코드 한 줄 안 짜도 된다.
여러 번 주고받은 세션에서 구조적 diff 버튼을 눌러, 매 요청마다 어떤 메시지·툴 결과가 새로 붙는지 본다. 그리고 뷰어의 토큰 사용량 분석(입력·출력·캐시 읽기·캐시 생성)을 보며 "어디서 토큰이 폭발하는지" 짚어본다. 프롬프트 캐시가 어떻게 동작하는지 감이 잡힌다.
--tap-client codex, --tap-client gemini 등으로 서로 다른 CLI를 같은 작업에 돌린 뒤, 각자의 시스템 프롬프트·툴 스키마를 나란히 비교한다. "어느 에이전트가 컨텍스트를 더 알뜰하게 쓰는가"를 추측이 아니라 원본 증거로 판정해 본다.
cli_clients.py의 ClientConfig와 CLIENT_CONFIGS를 읽고, 기존 클라이언트 하나가 어떤 필드로 정의됐는지 표로 정리한다. 그런 다음 docs/support-matrix.md와 대조하며 "리버스 vs 포워드 모드가 왜 그렇게 정해졌는지"를 스스로 설명해 본다. 확장 설계를 읽는 훈련.
certs.py(CA 생성) + forward_proxy.py(CONNECT·TLS 종료)를 참고해, aiohttp + cryptography로 100줄짜리 미니 MITM 프록시를 스스로 짜본다. 로컬에서 자기 자신의 HTTPS 요청 하나를 가로채 헤더를 콘솔에 찍는 것까지가 목표. 성공하면 "HTTPS를 못 본다"는 통념이 완전히 깨진다. 반드시 내 기기·내 트래픽으로만.
| 주차 | 주제 | 학습 내용 |
|---|---|---|
| 1주차 | HTTP·TLS 기초 | HTTP 메서드·헤더·상태코드 / TLS 핸드셰이크 / CONNECT 터널링 / 인증서 체인과 CA의 역할. 도구: curl -v, openssl s_client |
| 2주차 | 프록시와 MITM | 포워드 vs 리버스 프록시 차이 / mitmproxy로 실습 / certs.py 읽으며 CA·호스트 인증서 생성 원리 이해 / 왜 SigV4·OAuth는 URL을 못 바꾸는지 |
| 3주차 | Python asyncio 스트리밍 | asyncio 태스크·큐 / aiohttp 서버+클라이언트 / SSE·WebSocket 파싱 / sse.py의 재조립 로직 정독 / "흘리면서 복사"하는 비차단 패턴 |
| 4주차 | LLM API·컨텍스트 엔지니어링 | Anthropic Messages·OpenAI Responses/Chat 포맷 차이 / 툴 콜·툴 결과 구조 / 토큰·프롬프트 캐시 / claude-tap 뷰어로 실제 트래픽을 뜯어보며 "컨텍스트 설계" 감각 익히기 |
이 순서를 밟으면 claude-tap은 단순한 "편리한 도구"가 아니라 네트워크·비동기·보안·LLM을 잇는 살아있는 교과서가 된다. 각 주차의 개념이 정확히 어느 소스 파일에 구현돼 있는지 대응되기 때문에, "이론 → 실제 코드" 왕복 학습이 가능하다.
| 용어 | 의미 |
|---|---|
| 프록시(proxy) | 두 컴퓨터 사이에서 통신을 중계하는 중간 서버. claude-tap의 핵심 형태 |
| 리버스 프록시 | 클라이언트가 "프록시를 진짜 서버로 알고" 접속. base URL 바꿔치기 방식 |
| 포워드 프록시 | 클라이언트가 진짜 주소를 믿되 HTTPS_PROXY로 우리를 거침. 주소 못 바꾸는 CLI용 |
| MITM / TLS 종료 | 암호화 트래픽을 중간에서 복호화→재암호화. 여기선 "내 기기, 내 트래픽" 자기 관찰용 |
| CA 인증서 | 다른 인증서를 서명·발급하는 최상위 인증서. 기기가 신뢰해야 MITM이 통함 |
| CONNECT | HTTPS 프록시에게 "목적지로 터널을 뚫어달라"고 요청하는 HTTP 메서드 |
| SSE | 서버가 클라이언트로 한 방향 실시간 스트림을 밀어보내는 표준. AI 응답 스트리밍의 정체 |
| 재조립(reassemble) | 조각조각 오는 스트림 이벤트를 누적해 "완전한 응답 하나"로 복원. SSEReassembler |
| 트레이스(trace) | 요청-응답 한 쌍의 기록. 시스템 프롬프트·툴·메시지·토큰까지 담김 |
| redaction(마스킹) | 저장 전에 민감 헤더(Authorization·x-api-key 등)를 가리는 처리 |
| allowlist | 아는 API 경로만 통과시키는 목록. 스캐너의 엉뚱한 경로는 404로 차단 |
| upstream(상류) | 프록시가 최종적으로 요청을 전달하는 진짜 API 서버 |
| strip_path_prefix | 리버스 모드에서 경로 앞부분(예 /v1)을 떼는 정규화 규칙 |
| ClientConfig | 16종 클라이언트의 인증·엔드포인트·프록시 모드를 담은 선언적 설정 dataclass |
| compact trace(.ctap.json) | blob 중복을 제거한 포터블 트레이스 번들. 나중에 HTML로 렌더 가능 |
| self-contained 뷰어 | 외부 의존성 0인 단일 HTML. 어디서든 브라우저만으로 열림 |
| EventStream(Bedrock) | AWS Bedrock의 바이너리 스트림 포맷. bedrock.py가 디코드해 토큰·모델 추출 |
uv tool install claude-tapREADME.md(사용법·아키텍처) · docs/support-matrix.md(클라이언트×인증×상류 검증표) · AGENTS.md(기여 규칙)claude_tap/forward_proxy.py(MITM) · certs.py(CA 생성) · sse.py(스트림 재조립) · cli_clients.py(ClientConfig) · proxy.py(헤더 마스킹·allowlist)