업데이트로 돌아가기
New releaseJul 26, 2026

mcpsnoop v0.15.0

MCP를 위한 Wireshark. AI 클라이언트와 MCP 서버 간의 모든 실제 도구 호출을 실시간으로 터미널에서 보여주는 투명 프록시입니다.

공유

mcpsnoop

MCP용 Wireshark. AI 클라이언트와 MCP 서버 간의 모든 실제 도구 호출을 터미널에서 실시간으로 보여 주는 투명한 프록시입니다.

CI Go Reference MIT

mcpsnoop demo

문제

공식 MCP Inspector는 자체 클라이언트로 연결되므로, 여러분의 클라이언트(Cursor, Claude Code, Codex)가 서버에 실제로 보내는 내용을 결코 볼 수 없습니다. 그리고 요청이 도착하기를 기다리는 방식은 모델이 호출하지 않은 호출이나 잘못된 인수로 수행한 호출을 표시할 수 없습니다. 도구가 조용히 호출되지 않으면 기능이 맞지 않거나, 호출이 그냥 멈춰 버리면 로그를 뒤지고 추측하게 됩니다.

mcpsnoop는 대신 실제 데이터 경로에 위치합니다. 서버 명령을 mcpsnoop로 감싸고 실제 클라이언트와 서버가 통신하는 동안 모든 JSON-RPC 프레임을 실시간으로 확인하세요.

빠른 시작

별도 설정 없이 바로 확인할 수 있습니다.```bash mcpsnoop demo

실제로 사용하려면, 서버를 클라이언트의 MCP 구성에 래핑하세요.```json
{
  "mcpServers": {
    "my-server": {
      "command": "mcpsnoop",
      "args": ["--", "node", "build/index.js"]
    }
  }
}

-- 뒤의 모든 내용은 일반적으로 서버를 시작하는 명령어입니다. python server.py, npx -y @scope/server 또는 컴파일된 바이너리처럼 이미 사용 중인 것으로 바꾸세요.

Claude Desktop에서는 해당 수정을 직접 하지 않아도 됩니다.```bash mcpsnoop wrap my-server # route my-server through mcpsnoop mcpsnoop unwrap my-server # put it back

`wrap`는 `claude_desktop_config.json`을 찾아 이를
처음에 `claude_desktop_config.json.mcpsnoop.bak`으로 복사하고, 해당 서버 하나의 항목만
다시 작성하므로 나머지 서버와 서식은 그대로 유지됩니다.
다시 작성된 항목 내에서 키는 알파벳 순서로 돌아옵니다. `unwrap`은
파일을 복원하고, 더 이상 래핑된 서버가 없으면 백업을 제거합니다.
두 작업 후에는 Claude Desktop을 다시 시작하세요. MCP 서버는 시작 시 한 번만
실행되기 때문입니다.

그런 다음 평소처럼 클라이언트를 사용하고 UI를 엽니다.```bash
mcpsnoop

플래그도, 소켓 경로도, 기억해야 할 시작 순서도 없습니다. shim과 UI는 서로를 자동으로 찾으며, UI는 디스크에서 과거 세션을 백필합니다.

streamable-HTTP 서버의 경우 mcpsnoop를 리버스 프록시로 실행하세요.```bash mcpsnoop http --target http://localhost:3000/mcp --listen :7000

모든 응답의 HTTP 상태가 스트림에 표시되므로, 자체적으로 JSON-RPC 메시지를 담지 않은 응답도 아무것도 아닌 것이 아니라 보이는 프레임으로 남는다: 401 챌린지, 거부된 Origin에 대한 403, 알림을 확인하는 202, 그리고 대상에 전혀 도달할 수 없을 때의 502. 401의 `WWW-Authenticate` 헤더는 그대로 유지되어 인스펙터에 표시된다. 이 헤더는 인증 방식과 다음에 이동할 리소스 메타데이터를 명시하기 때문이다. TUI에서 `status:401`로 상태별 필터링하거나, `status:err`로 모든 실패를 필터링할 수 있다. 4xx 또는 5xx는 오류로 간주되므로, 기본 `mcpsnoop check` 실행은 해당 응답에서 실패한다.

자체 서버가 없으신가요? 공개된 테스트 서버를 대상으로 자신의 클라이언트를 사용해 [실제로 시도해 보세요](https://github.com/kerlenton/mcpsnoop/blob/HEAD/docs/TRY_IT.md). 세션이 끝난 후 이를 검사하려면 [로그에서 과거 세션 검토](https://github.com/kerlenton/mcpsnoop/blob/HEAD/docs/POST_MORTEM.md)를 참조하세요.

### 설정 파일

프로젝트 전반에 걸쳐 동일한 shim 플래그를 재사용한다면, 현재 작업 디렉터리의 `.mcpsnoop.toml` 파일에 넣으세요.```toml
label = "filesystem"
trace-file = "trace.jsonl"
redact-secrets = true
redact-key = "token,authorization"
redact-value = "sk-[A-Za-z0-9]+"
redact-path = "$.params.arguments.password"
no-trace = false

Repeat redact-key, redact-value, redact-path를 각각 별도의 줄에 반복하여 여러 개를 추가할 수 있습니다.

이것이 지원하는 모든 키입니다.

이 파일은 상위 디렉터리가 아닌 현재 작업 디렉터리에서만 검색됩니다.

명시적인 명령줄 플래그가 구성 파일의 값을 재정의합니다.

명령

명령기능
mcpsnoop -- <server>stdio 서버를 투명한 셰임(shim)으로 감쌉니다
mcpsnoop라이브 TUI를 엽니다
mcpsnoop http --target <url>streamable-HTTP 서버를 프록시합니다
mcpsnoop export세션을 json, html, text, har 또는 otlp로 렌더링합니다
mcpsnoop check오류, 잘못된 프레임, 경고, 라우팅 불일치, 응답 없는 호출 또는 지연 결과가 있을 때 CI를 실패시킵니다
mcpsnoop baseline신뢰할 수 있는 도구 정의를 검사, 승인 또는 재설정합니다
mcpsnoop diff두 캡처된 세션 간의 도구와 호출을 비교합니다
mcpsnoop open저장된 세션을 TUI에서 엽니다
mcpsnoop prune기준일보다 오래된 저장된 세션 로그를 삭제합니다
mcpsnoop wrap <server>Claude Desktop의 서버 중 하나를 mcpsnoop을 통해 라우팅합니다
mcpsnoop unwrap <server>해당 서버의 항목을 원래대로 되돌립니다
mcpsnoop remote <user@host>SSH 터널 명령을 출력합니다
mcpsnoop demo스크립트된 세션을 재생합니다

전체 목록은 mcpsnoop help를, 특정 명령의 플래그는 mcpsnoop help <command>를 실행하세요.

비교

MCP Inspectormcpsnoop
실제 클라이언트와 서버 트래픽을 봄아니요
응답 없는 호출 및 스트림 오류 표시아니요
스트림을 손상시키는 잘못된 출력 표시아니요
잘못된 JSON-RPC 프레임 표시아니요
승인 후 도구 정의 변경 감지아니요
대화형 터미널 UI아니요
구성 불필요, 플래그나 순서 없음아니요
기능 검사기부분
캡처된 호출 재생아니요
세션 내보내기 (json / html / text / otlp)아니요
단일 바이너리, 런타임 종속성 없음아니요

설치

Go```bash

go install github.com/kerlenton/mcpsnoop/cmd/mcpsnoop@latest

### Homebrew```bash
brew install mcpsnoop

모든 플랫폼용 사전 빌드 바이너리는 Releases 페이지에서 확인할 수 있습니다.

셸 완성

mcpsnoop는 bash, zsh, fish, PowerShell용 완성 스크립트를 제공합니다. 설정 단계는 mcpsnoop completion <shell> --help를 실행하면 확인할 수 있으며, 완성 기능 활성화 방법과 OS별 설치 경로를 다룹니다.

작동 방식

mcpsnoop는 AI 클라이언트와 MCP 서버 사이의 파이프에 위치하여 모든 JSON-RPC 프레임을 실시간 터미널 UI로 복사합니다

mcpsnoop는 하나의 바이너리로 두 가지 역할을 수행합니다. mcpsnoop -- <server>는 클라이언트가 실행하는 투명한 shim으로, 바이트를 그대로 전달하면서 모든 프레임의 사본을 허브로 보냅니다. 인자 없이 실행하는 mcpsnoop는 바로 그 허브이자 실시간 TUI입니다. 두 프로세스는 잘 알려진 소켓과 디스크 로그를 통해 서로 연결되므로, 어느 쪽이 먼저 시작할 필요가 없습니다.

허브는 기본적으로 가장 최근에 저장된 100개의 세션을 로드하여, 이전 추적 기록을 삭제하지 않으면서도 시작 시 작업량을 제한합니다. 다른 한도를 선택하려면 mcpsnoop --history-limit N을 사용하고, 전체 기록을 로드하려면 mcpsnoop --history-limit 0을 사용하세요. 이전 세션은 mcpsnoop open <session-id>mcpsnoop export <session-id>를 통해 계속 사용할 수 있습니다.

기록 한도는 로드되는 항목의 범위를 제한하고, mcpsnoop prune은 보관되는 항목의 범위를 제한합니다. prune은 기준 시점보다 오래된 저장 세션 로그를 삭제하며, 자체적으로 실행되지 않습니다.```bash mcpsnoop prune --older-than 30d --dry-run # list what would go, remove nothing mcpsnoop prune --older-than 30d # delete after confirming mcpsnoop prune --older-than 72h --yes # skip the prompt in a script

`--older-than`는 필수이며(아무것도 삭제하는 기본값은 없습니다)
`30d` 같은 일수 또는 `72h` 같은 Go 기간을 허용합니다. 도구 기준선은
그대로 유지됩니다. 기준선은 세션이 아니라 서버 라벨을 기준으로 키가 지정되기 때문입니다.

실제 파이프 안에 있으며, Inspector처럼 옆에 붙어 있는 것이 아니므로
실제 클라이언트와 서버가 서로 주고받는 내용을 정확히 볼 수 있으며, 서버가
어떤 언어로 작성되었든지 간에 상관없습니다.

## 키 바인딩

| 키 | 동작 | | 키 | 동작 |
|---|---|---|---|---|
| `enter` | 검사 / 드릴다운 | | `/` | 필터 |
| `esc` | 뒤로 | | `:` | 명령 |
| `j` / `k` | 이동 | | `r` | 호출 재생 |
| `g` / `G` | 맨 위 / 맨 아래 | | `c` | 기능 |
| `ctrl-f` / `ctrl-b` | 페이지 | | `s` | 도구 요약 |
| `p` | 일시정지 | | `y` | 복사 |
| `shift`+`<key>` | 열 기준 정렬 | | `e` | 내보내기 |
| `ctrl-d` | 세션 삭제 | | `f` | 추적 |
| `?` | 도움말 | | | |

앱에서 `?`를 누르면 전체 목록을 볼 수 있습니다.

## 스트림 필터링

세션에서 `/`를 누르고 공백으로 구분된 토큰을 AND 조건으로 결합하세요. 일반 텍스트는
메서드, 도구, ID 및 페이로드와 일치합니다.

| 토큰 | 필터 기준 | 예시 |
|---|---|---|
| `tool:` | 도구 이름 | `tool:search` |
| `method:` | JSON-RPC 메서드 | `method:tools/call` |
| `id:` | 요청 ID 및 이를 이어가는 모든 재시도 | `id:7` |
| `task:` | 작업 ID | `task:01J...` |
| `dir:` | 방향 (`c2s`, `s2c`) | `dir:s2c` |
| `kind:` | 프레임 유형 (`req`, `resp`, `notify`, `stderr`, `invalid`) | `kind:invalid` |
| `status:` | 호출 결과 (`ok`, `error`, `cancel`, `late`, `cancelled`, `pending`, `bad`, `warn`, `mismatch`, 또는 `401` 같은 HTTP 상태 코드) | `status:error` |

토큰을 조합하면 원하는 대상을 정확히 지정할 수 있습니다.```text
tool:search status:pending        # in-flight calls to one search tool
status:cancel                     # calls the client gave up on (status:cancelled is a cancelled task)
status:late                       # results that arrived after the cancellation
method:tools/call status:error    # tool calls that failed
dir:s2c kind:req                  # server-initiated requests (servers before 2026-07-28)

마지막 것은 2025-11-25 또는 그 이전 버전을 사용하는 서버에서만 무언가를 찾습니다. 2026-07-28 개정판은 서버 주도 요청을 제거했으며, 이제 클라이언트로부터 무언가가 필요한 서버는 클라이언트의 요청에 그 내용을 요청하는 방식으로 응답한 다음 클라이언트가 재시도합니다. mcpsnoop는 이러한 재시도를 이어지는 요청에 연결하므로, 교환이 여러 번이 아닌 한 번의 호출로 읽힙니다.

세션 내보내기

캡처한 모든 세션을 휴대용 파일로 변환하세요.```bash mcpsnoop export -T json|html|text|har|otlp [-o file|-] [session-id|log.jsonl|-]

| Format | What you get |
|---|---|
| `json` | 연관 호출, 도구별 개수 및 p50/p95/p99 지연 시간, 가장 느린 호출, 기능, 원시 프레임 |
| `html` | 검색 및 접을 수 있는 JSON을 지원하는 자체 포함 브라우저 파일 |
| `text` | 보기 좋은 순수 텍스트 덤프 |
| `har` | 연관 호출당 하나의 항목이 있으며, 브라우저 개발자 도구 및 HAR을 읽는 다른 도구에서 열 수 있음 |
| `otlp` | 연관 호출당 하나의 스팬이 있는 OTLP JSON. W3C 추적 컨텍스트는 호출자 추적을 연결하며, 그렇지 않으면 세션당 하나의 추적이 사용됨 |

MCP는 HTTP가 아니므로 HAR 항목의 URL, 상태 코드 및 타이밍은 의도적인
매핑이지 실제 유선 전송 기록이 아닙니다.

OTLP의 경우 요청의 `_meta.traceparent`는 해당 호출의 추적 및 상위
스팬 ID를 제공하며, `_meta.tracestate`는 스팬에 함께 전달됩니다. traceparent가
없거나 유효하지 않으면 mcpsnoop는 세션에서 파생된 추적을 유지하고 어떤 상태도 전달하지 않습니다.
mcpsnoop는 관여하지 않고 관찰만 하므로 자체 벤더 항목을 추가하지 않으며
호출자의 상태를 변경하지 않고 그대로 전달합니다.```bash
mcpsnoop export -T html -o out.html                    # an HTML file to open in a browser
mcpsnoop export -T text server.py-48213-7f3a1c9e2b04   # a specific session, as text
mcpsnoop export -T json | jq                           # the newest session, piped to jq
mcpsnoop export -T har -o session.har                  # a HAR file to open in browser devtools
mcpsnoop export -T otlp -o trace.json                  # import into an OTLP-compatible tracing backend

-o를 생략하면 stdout으로 출력하고, 세션을 생략하면 가장 최근 것을 사용하며, -를 전달하면 stdin에서 JSONL을 읽습니다. TUI에서 e를 눌러 선택한 세션을 HTML로 내보내거나, 명령 모드에서 :export json|html|text|har|otlp [path]를 실행하세요.

기존 캡처를 검사하거나 공유하기 전에 정리하려면, 캡처 중 사용한 것과 동일한 redaction 플래그를 export 또는 open에 전달하십시오:```bash mcpsnoop export session.jsonl --redact-secrets --redact-key project_token -o shared.json mcpsnoop open session.jsonl --redact-path '$.params.arguments.password'

이 플래그들은 내보낸 파일 또는 메모리 내 TUI 뷰를 재작성하며, 소스 JSONL은 절대 변경하지 않습니다. `export`는 입력과 같은 파일을 출력으로 지정하는 것을 거부하며, 제자리로 이름이 바뀌는 임시 파일을 통해 작성하므로, 실패한 실행은 이전 파일을 그대로 유지합니다.

`tools/list` 결과에 표시되는 도구의 `inputSchema`와 `outputSchema`는 `--redact-key`와 `--redact-secrets`의 영향을 받지 않습니다. 스키마 내부의 이름은 값이 아닌 타입 선언이므로, 이름 자체는 어느 쪽이든 로그에 남으며, `token`이라는 속성 아래의 하위 스키마를 삭제하면 도구 자체의 검사도 함께 삭제됩니다. 예외는 해당 위치뿐이므로, 우연히 `inputSchema`라는 이름을 가진 인자는 다른 인자와 마찬가지로 삭제되며, 구조가 아닌 데이터를 담고 있는 `default`, `const`, `examples`, `enum`에서 중단됩니다. 스키마 내부의 특정 항목을 지정하려면 `--redact-path`를 사용하고, mcpsnoop이 파싱하는 `type`과 `x-mcp-header`라는 두 키워드를 제외한 모든 위치의 텍스트와 일치하는 `--redact-value`를 사용하세요.

각 플래그가 도달하는 범위는 다르므로, 가정하지 말고 결과를 확인하세요. 네 가지 모두 JSON-RPC 페이로드를 삭제하며, `--redact-key`, `--redact-path`, `--redact-secrets`는 오직 페이로드에만 도달합니다. `--redact-value`만이 stderr, 기타 비-JSON 텍스트, 문자열 내부까지 삭제합니다. `Mcp-Param-*` 헤더는 그것이 반영하는 본문 값과 함께 삭제되며, 다른 봉투 메타데이터, 서버 레이블, `Mcp-Name`, `Mcp-Method`, HTTP 상태는 캡처된 그대로 남습니다. 삭제는 최선 노력 방식이므로, 별도의 출력 경로를 사용하고 공유 전에 결과를 읽어보세요.

### 완료된 호출을 OTLP 수집기로 스트리밍

프록시가 실행되는 동안 OTLP/HTTP JSON 트레이스 엔드포인트를 가리켜 스팬을 전송하세요. 수집기 인증 또는 테넌트 헤더를 위해 `--otlp-header`를 반복하세요.```bash
mcpsnoop \
  --otlp-endpoint http://localhost:4318/v1/traces \
  --otlp-header "Authorization=Bearer $OTLP_TOKEN" \
  -- node build/index.js

mcpsnoop http \
  --target http://localhost:3000/mcp \
  --otlp-endpoint http://localhost:4318/v1/traces

전달은 best-effort 방식이며 프록시된 MCP 트래픽을 결코 차단하지 않습니다. 수집기가 사용 불가능하면 mcpsnoop은 백그라운드에서 재시도하고, 바운드 큐가 가득 차면 새 추적 프레임을 삭제합니다. 일반 JSONL 세션 로그는 지속적인 기록으로 남습니다.

세션 비교

id 또는 JSONL 경로로 저장된 두 세션을 비교합니다.```bash mcpsnoop diff before-session after-session mcpsnoop diff old.jsonl new.jsonl

이 보고서는 추가되거나 제거된 도구, 설명 및 `inputSchema` 변경 사항, 상태가 변경된 일치하는 도구 호출, 주목할 만한 소요 시간 변화를 보여줍니다. 호출은 도구 이름과 인수(argument)로 매칭되므로 순서가 바뀐 호출도 올바르게 비교됩니다. 기본적으로 소요 시간 변화는 최소 100ms 및 2배 이상 달라야 하며, `--duration-threshold`와 `--duration-ratio`로 해당 기준을 조정할 수 있습니다.

`--exit-code`를 전달하면 CI를 회귀(regression) 기준으로 게이트할 수 있습니다. after 세션이 도구를 제거하거나, 도구 설명, 제목, 입력 스키마, 출력 스키마 또는 어노테이션을 변경하거나, 상태가 악화된 호출이 있거나, 속도가 느려진 경우 0이 아닌 종료 코드를 반환합니다. 개선 사항(추가된 도구, 수정된 호출, 속도 향상)은 여전히 0으로 종료되며, 동작은 바꾸지 않고 도구의 모양만 바꾸는 아이콘 변경도 마찬가지로 0으로 종료됩니다.

## CI에서 세션 확인

기록된 에이전트 실행을 오류, 스트림 손상, 프로토콜 경고, 라우팅 헤더 불일치, 응답을 받지 못한 호출, 캡처를 불완전하게 남기는 드롭된 프레임, 도구 정의 드리프트 또는 더 이상 사용되지 않는 프로토콜 기능 사용을 기준으로 게이트합니다.```bash
mcpsnoop check [--format text|junit|sarif] [--fail-on error,invalid,warn,mismatch,pending,late-result,drift,deprecated,incomplete,schema] [session-id|log.jsonl|-]

error, invalidwarn은 자체적으로 검사를 실패시킵니다. 나머지는 선택적(opt-in)입니다. 쉼표로 구분된 하위 집합을 전달하면 작업이 관심을 두는 것만 게이트할 수 있고, 세션을 생략하면 가장 최신 캡처를 확인하며, -를 사용하면 stdin에서 JSONL을 읽습니다.

신호실패 조건
errorJSON-RPC 오류로 응답된 호출, isError로 표시된 결과, 또는 실패로 끝난 작업
invalid프로토콜 채널에서 유효한 JSON-RPC가 아닌 프레임, 일반적으로 서버가 stdout에 로깅하는 경우
warnMCP 또는 JSON-RPC 사양이 설정한 기대를 위반하는 프레임
mismatch본문과 일치하지 않는 라우팅 헤더, 배치에 실려 있는 경우, 또는 개정판이 요구하는 위치에 누락된 경우
pending캡처가 종료되었을 때 아직 열려 있어 호출자가 기다리게 된 요청
late-result요청이 취소된 후 도착한 응답
drift기준선(baseline)이 승인된 후 광고된 도구 정의가 변경된 경우
deprecated사양이 더 이상 사용하지 않는(deprecated) 기능
incomplete업스트림에서 드롭된 프레임으로, 다른 모든 개수를 총합이 아닌 하한선으로 만든다
schema클라이언트 간에 제대로 전달되지 않는 구성 또는 방언을 사용하는 광고된 스키마

모든 신호는 게이팅 여부와 관계없이 집계되므로, 어떤 것을 실패로 처리할지 결정하기 전에 실행이 무엇을 발견했는지 알 수 있습니다.``` session build-agent: errors=1 invalid=0 warnings=0 mismatches=0 pending=0 late_results=0 deprecated=0 missing_frames=0 schema_findings=1 schema findings: oneOf: search check failed: error

드롭된 프레임 수는 아티팩트와 함께 전달되므로, 실제보다 적게 보고하는 캡처는 어디서 열어도 그 사실이 드러납니다: JSON 내보내기의 `missing_frames`, HAR의 `log.comment`, OTLP의 `mcpsnoop.session.missing_frames` 리소스 속성이 그것입니다.```bash
mcpsnoop check build-agent
mcpsnoop check --fail-on error,invalid artifacts/session.jsonl
mcpsnoop check --fail-on mismatch gateway-run.jsonl

신호 카운트 외에도, 실행의 형태를 검증하세요. 이들은 서로 및 --fail-on과 함께 조합되며, 실패가 발생하면 0이 아닌 종료 코드로 종료됩니다.

플래그실패 조건
--max-duration <dur>완료된 도구 호출 하나 이상이 예산을 초과한 경우; 해당 호출 수와 최악의 호출을 보고합니다
--expect-tool <name>지정된 도구가 한 번도 호출되지 않은 경우 (반복 가능)
--forbid-tool <name>지정된 도구가 호출된 경우 (반복 가능)

a contract for the run: search must run, delete must not, nothing over 2s

mcpsnoop check --expect-tool search --forbid-tool delete --max-duration 2s run.jsonl

### CI가 이미 확인하는 곳에 보고

`--format junit`은 신호 및 세션마다 하나의 `<testcase>`를 작성하며, 그 실패는
텍스트 출력과 동일한 `--fail-on` 선택을 따릅니다.```yaml
- name: Check captured MCP session
  run: |
    mkdir -p test-results
    mcpsnoop check --format junit artifacts/session.jsonl > test-results/mcpsnoop.xml
- name: Upload mcpsnoop JUnit report
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: mcpsnoop-junit
    path: test-results/mcpsnoop.xml

--format sarif는 대신 SARIF 2.1.0 로그를 작성합니다. junit이 신호당 하나의 집계를 보고하는 반면, SARIF는 세션, 프레임 Seq 및 프레임 자체의 경고 또는 드리프트 텍스트를 담고, 프레임이 디코딩된 로그의 줄을 가리키는 결과를 발견 항목당 하나씩 보고합니다. --fail-on에 지정된 신호는 error 수준으로 보고되고, 그 외의 신호는 note 수준으로 보고되므로 보고서와 게이트가 서로 어긋나지 않습니다.

결과는 작업 디렉터리를 기준으로 한 상대 경로로 로그를 가리키며, code scanning은 이를 저장소 루트에 대해 확인합니다. 경고는 해당 경로가 분석된 커밋의 파일인 경우에만 주변 줄과 함께 렌더링되므로, 워크플로가 artifacts/에 생성한 캡처는 메시지, 규칙 및 줄 번호를 담은 경고를 열지만 소스 보기는 없습니다. 전체를 렌더링하려는 캡처를 커밋하는 것이 유일한 방법입니다. 상태 디렉터리나 stdin에서 읽은 로그에는 경로가 전혀 없습니다.

code scanning은 실행에 25,000개가 넘는 결과가 있는 파일을 거부하고 허용된 것 중 상위 5,000개만 표시하므로 보고서는 5,000개로 제한됩니다. 게이트가 먼저 실패한 발견 항목, 그다음 빠진 항목 수를 알리는 mcpsnoop/report-truncated 결과가 포함됩니다. 텍스트 및 junit 형식은 완전하게 유지됩니다.

발견 항목을 Security 탭에 넣으려면 SARIF 로그를 upload-sarif에 넘기세요. 작업에는 security-events: write가 필요하며, 없으면 업로드가 403으로 응답합니다. check는 발견 항목이 있으면 0이 아닌 값으로 종료되므로, 업로드 단계는 보고할 내용이 있는 실행에서 실행되도록 if: always()가 필요합니다. continue-on-error는 판정을 code scanning 검사에 넘기며, 이 검사는 error 수준 경고에서 실패하고 필수 검사로 지정할 수 있습니다. 체크 단계 자체가 작업을 실패 상태로 만들기를 원한다면 이 옵션을 제거하세요.```yaml permissions:

required for all workflows

security-events: write

only required for workflows in private repositories

actions: read contents: read

steps:

  • name: Check captured MCP session continue-on-error: true run: mcpsnoop check --format sarif artifacts/session.jsonl > mcpsnoop.sarif
  • name: Upload mcpsnoop SARIF report if: always() uses: github/codeql-action/upload-sarif@v4 with: sarif_file: mcpsnoop.sarif category: mcpsnoop
### 본문과 일치하지 않는 라우팅 헤더 감지

streamable-HTTP 전송에서 게이트웨이는 `Mcp-Method`와 `Mcp-Name`을 기준으로 라우팅하는 반면 서버는 본문을 읽으므로, 본문과 일치하지 않는 헤더는 두 주체가 서로 다른 요청을 보고 있음을 의미합니다. `mismatch` 신호는 이러한 경우, 주소를 지정할 수 없는 배치에 실려 오는 헤더, 그리고 필수 헤더가 완전히 누락된 경우를 포착합니다.

2026-07-28 기준으로 라우팅 헤더 누락은 검증 실패이며, 규격 준수 서버는 `400` 및 `-32020`으로 요청을 거부합니다. mcpsnoop은 세션이 해당 리비전 이상을 사용하는 것으로 확인된 경우에만 이를 발생시킵니다. 이전 리비전에서는 이러한 헤더가 전혀 정의되지 않았고, 그 환경에서 생략하는 것이 올바르기 때문입니다. 서버가 자체적으로 `-32020`으로 거부하는 경우도 동일한 신호로 간주됩니다.

HTTP 필드 값에 맞지 않는 이름이나 리소스 URI는 `=?base64?…?=` 센티널에 Base64로 실려 전송되며, 비교 전에 디코딩되므로 올바르게 인코딩하는 클라이언트는 결코 플래그로 표시되지 않습니다.

HTTP `tools/call` 요청에서 mcpsnoop은 각 `Mcp-Param-{Name}` 헤더도 표시하며, 일치하는 광고된 도구 정의가 알려진 경우 이를 주석이 달린 인수 경로와 비교합니다. 중첩 속성, Base64 센티널, 불리언 및 숫자와 동등한 안전 정수는 문자열 비교 오탐 없이 처리됩니다. 알 수 없는 파라미터 헤더와 일치하는 도구 정의가 없는 세션은 관찰용으로만 남습니다. 키 및 값 기반 마스킹(redaction)은 캡처된 파라미터 헤더 값이 sink에 도달하기 전에 적용되며, mcpsnoop이 스스로 마스킹한 값은 불일치로 보고되지 않습니다.

### 도구 정의 드리프트 감지

서버 라벨에 대해 관찰된 첫 번째 완전한 `tools/list`가 신뢰할 수 있는 기준선이 됩니다. 이후 세션에서는 해당 기준선을 필드별로 비교합니다: 설명, 제목, 입력 및 출력 스키마, 주석과 아이콘, 그리고 추가되거나 제거된 도구까지 포함합니다. 주석이 가장 중요합니다. `readOnlyHint`로 승인된 도구가 나중에 자신을 파괴적이라고 선언하는 것이 바로 이 검사가 존재하는 이유인 rug-pull(뒤통수 치기)이기 때문이며, 스펙은 클라이언트에게 주석을 신뢰할 수 없는 것으로 취급하라고 지시합니다. 제목과 아이콘은 사용자가 보는 것이므로 추적되며, 스펙은 도구의 `title`을 `annotations.title` 및 이름보다 우선시합니다. sessions 테이블과 도구 요약은 MCP 트래픽을 차단하거나 변경하지 않고 드리프트를 플래그로 표시합니다.

주석은 스펙 기본값을 통해 비교되므로, 이미 의존하고 있던 힌트를 명시적으로 표기하기 시작한 서버는 보고되지 않습니다. mcpsnoop이 필드를 추적하기 전에 기록된 기준선은 기록된 필드에 대해서는 계속 작동하며, 답할 수 없는 필드가 무엇인지도 알려줍니다. 현재 정의를 신뢰하게 되면 `mcpsnoop baseline --accept`로 다시 기록하세요.

마스킹이 기록하는 내용을 변경하면 드리프트가 비교하는 내용도 변경됩니다. `--redact-value` 없이 기록한 기준선을 `--redact-value`로 캡처한 결과와 비교하면 마스킹된 필드가 변경된 것으로 보고되는데, 이는 올바른 동작입니다. 기록된 정의가 실제로 변경되었기 때문입니다. 마스킹 설정을 변경한 후에는 `--accept`로 다시 기록하세요.

명령 이름이나 대상 호스트가 충돌할 수 있는 각 서버에는 안정적이고 고유한 `--label`을 사용하세요. 기준선은 일반적인 mcpsnoop 상태 디렉터리에 저장되므로 `MCPSNOOP_HOME` 및 `XDG_STATE_HOME`이 적용됩니다.```bash
mcpsnoop check --fail-on drift session.jsonl
mcpsnoop baseline session.jsonl
mcpsnoop baseline --accept session.jsonl  # trust a legitimate definition change
mcpsnoop baseline --reset session.jsonl   # trust the next complete tools/list

일시적인 CI 환경에서는 상태 디렉터리가 비어 있으므로 첫 번째 실행은 기준선만 기록하며
드리프트가 없다고 보고합니다. 기준선은 이후 실행에서 검증할 수 있도록
실행 간에 유지되어야 합니다. 체크인되거나 캐시된 디렉터리에 --baseline을 지정하거나,
지속되는 경로에 MCPSNOOP_HOME을 설정하세요.```bash mcpsnoop check --fail-on drift --baseline .mcpsnoop/baselines session.jsonl

`drift`는 `check`에 대해 옵트인(opt-in) 방식입니다. 기본 `error,invalid,warn` 게이트는 변경되지 않습니다.

### 폐기 예정 프로토콜 기능 플래그 지정

2026-07-28 개정판에서는 Roots, Sampling, Logging을 폐기 예정(deprecated)으로 지정합니다. 이 기능들은 최소 1년간 계속 작동하므로, mcpsnoop은 이를 오류로 취급하는 대신 플래그로 표시합니다. 스트림, 기능 검사기, 내보내기 모두 이들을 플래그로 표시하며, 각 플래그는 대체 항목을 명시합니다.

세 기능 중 두 가지는 이제 다중 왕복 요청을 통해서만 접근할 수 있으며, 메서드 이름은 프레임 자체가 아닌 서버의 `inputRequests` 맵 안에 있습니다. 이러한 경우도 플래그로 표시되므로, 새 패턴으로 전환한 서버도 조용히 보고를 중단하지 않습니다.```bash
mcpsnoop check --fail-on deprecated session.jsonl

drift와 마찬가지로 deprecated도 옵트인(opt-in) 방식입니다. 기본 실행은 개수만 보고하고 녹색 상태를 유지하므로, 아직 허용되는 deprecated 기능을 사용하는 세션은 그 자체로 CI를 빨갛게 만들지 않습니다.

클라이언트가 잘 처리하지 못하는 스키마 구성에 플래그 지정

서버가 완전히 유효하더라도 에이전트(agent)가 사용하기 어려울 수 있습니다. 클라이언트마다 실제로 지원하는 JSON Schema의 범위가 다르며, 모델이 계속 잘못 호출하는 도구는 대개 클라이언트가 제공하는 것보다 더 많은 것을 요구하는 스키마를 가진 도구입니다.

s로 여는 도구 요약에는 SCHEMA 열이 있어, 광고된 각 도구의 스키마에서 가장 주목할 만한 점을 표시하며 종류가 둘 이상이면 끝에 +를 붙입니다.

표시의미
no rootinputSchema가 없거나, JSON 객체가 아니거나, 루트 타입이 "object"가 아닌 경우
dialect리비전이 기본으로 사용하는 2020-12 외의 방언(dialect)을 지정하는 $schema
ext ref문서 외부를 가리키는 $ref, 스펙이 구현자에게 맹목적으로 따르지 말라고 경고하는 경우이기도 함
oneOf, anyOf, allOf, not클라이언트마다 일관되지 않게 처리되는 합성(composition) 키워드
ref같은 문서 내부를 가리키는 $ref
untyped타입을 선언하지 않고, 허용하는 값을 설명할 다른 방법도 없는 속성

첫 번째를 제외한 나머지는 모두 판정(verdict)이 아니라 관찰(observation)입니다. oneOf를 사용하는 스키마가 잘못된 것은 아니며, 단지 클라이언트에 따라 다르게 읽힐 가능성이 높을 뿐입니다. 그리고 스키마는 원하는 어떤 방언이든 선언할 수 있습니다. no root는 예외입니다. Tool 정의는 inputSchema를 요구하고 루트 타입을 "object"로 고정하므로, 목록(listing)을 검증하는 클라이언트는 그 도구를 아예 거부하며 그 도구는 결코 호출 가능 상태가 되지 않습니다. 그 이유를 설명해 줄 만한 것이 와이어(wire) 상에는 아무것도 없습니다. 그래서 no root가 그 열의 맨 앞에 오며, mcpsnoop 자체의 redaction이 지워 버린 스키마는 절대 보고되지 않습니다. 읽을 수 없는 스키마는 잘못된 스키마가 아니기 때문입니다.

이러한 구분이 check가 그것들을 어떻게 처리할지 결정합니다. no roottools/list 프레임에 대한 경고이므로, 플래그가 전혀 없어도 기본 error,invalid,warn 게이트에서 실패합니다. 바로 그 점이 핵심입니다. 즉, 사용할 수 없는 도구를 제공하는 서버는 모든 핸드셰이크(handshake)에 정상적으로 응답하고, 단지 tools/call을 받지 못할 뿐입니다. 관찰 항목들은 schema_findings로 집계되어 schema findings: 아래에 보고되며, --fail-onschema를 추가해야만 실행이 실패합니다. 둘 다 --format junit--format sarif로 출력되며, export는 도구별 목록을 summary.definitions.per_tool[].findings 아래에 담습니다.```bash mcpsnoop check session.jsonl # a non-object root already fails this mcpsnoop check --fail-on schema session.jsonl # and now so do the observations

이 열은 경고 색상을 띠며 ERR 열의 빨간색을 사용하지 않습니다. mcpsnoop는 여전히 전달하는 트래픽을 변경하지 않습니다.

아무것도 해석되거나 가져오지 않습니다. 외부 `$ref`는 형태만으로 인식되며, 그것이 가리키는 스키마는 결코 읽히지 않습니다.

### 컨텍스트에서 서버가 얼마나 비용을 발생시키는지 확인하기

도구 정의는 매 대화마다 모델의 컨텍스트에 들어가고, 도구 결과는 매 호출마다 들어갑니다. 도구 요약(`s`)은 실제로 캡처한 세션에서 두 가지를 모두 측정합니다.

`definitions` 줄은 고정 비용입니다. 단 한 번의 호출이 이루어지기 전에 이 서버의 `tools/list`가 차지하는 비용입니다. `DEF` 열은 이를 도구별로 세분화하며, `RESULT`는 지금까지 각 도구의 응답이 발생시킨 비용입니다. 테이블은 오류와 지연 시간 순으로 정렬되므로 `DEF`를 훑어 비용이 많이 드는 정의를 찾으십시오. 내보내기에서는 가장 무거운 것이 먼저 나열됩니다. 테이블 아래의 한 줄은 합계로는 숨겨지는 가장 무거운 단일 결과를 명명합니다.

정의 수치는 의미 없는 공백이 제거된 JSON이므로, `tools/list`를 pretty-print하는 서버가 그렇지 않은 서버보다 더 비싼 것으로 계산되지 않으며, 동일한 서버는 캡처 간에도 동일하게 측정됩니다. `RESULT`는 도착한 그대로의 바이트입니다. 결과는 정규화할 가치가 있는 계약이라기보다 일회성 페이로드입니다.```bash
mcpsnoop export -T json | jq '.summary.definitions'

내보내기는 동일한 수치를 도구별로, 그리고 설명(description)과 스키마(schema) 바이트로 분리하여 담습니다. 그래서 큰 설명과 큰 스키마는 분리된 채로 유지되며, 어느 쪽이든 캡처 간에 추적할 수 있습니다. mcpsnoop diff는 두 세션 사이에 설명 또는 스키마가 변경되었는지 알려줍니다. 그 변경의 크기가 담긴 곳이 바로 내보내기입니다.

이것들은 바이트(bytes)이지 토큰(tokens)이 아닙니다. 토큰 수는 모델에 따라 달라지므로, 토큰을 측정하려면 토크나이저를 포함하고 누구의 것을 선택해야 합니다. 바이트는 정확하며, 자신만의 비율을 적용할 수 있습니다. 완료되지 않은 tools/list는 본 것을 하한으로 보고하고 그렇게 명시하며, 부분 합계를 총계인 것처럼 넘기지 않습니다.

서버 상태를 조작하는 클라이언트 감지

다중 왕복(multi round-trip) 패턴에서 서버는 클라이언트에게 불투명한 requestState를 건네주고, 클라이언트는 재시도 시 이를 변경하지 않고 그대로 되돌려 보내야 합니다. 서버는 이를 공격자가 제어하는 입력으로 취급하도록 지시받습니다. 그것을 변조하는 클라이언트가 서버 동작을 바꾸거나 인증 검사를 우회하려 시도할 수 있기 때문입니다.

파이프 속에 자리한 mcpsnoop는 값이 나갔다가 돌아오는 것을 볼 수 있으므로, 계약이 언제 위반되었는지 말해줄 수 있습니다. 위반될 수 있는 세 가지 방식이 있으며, 각각 재시도 시 프로토콜 경고로 보고됩니다.

보고의미
MRTR retry changed requestState클라이언트가 서버가 발급한 것과 다른 것을 되돌려 보냄
MRTR retry is missing requestState서버가 발급했는데 재시도에서 누락함
MRTR retry invented requestState재시도가 서버가 발급한 적 없는 것을 실어 보냄

이는 우리의 관찰이 아니라 클라이언트에 의한 프로토콜 위반이므로, 일반적인 경고 신호를 통해 전달되며 기본 check 실행은 하나만 있어도 실패합니다. 이는 의도적입니다. 서버 상태를 조작하는 클라이언트는 빌드를 중단할 가치가 있습니다.

값 자체는 표시되거나 기록되지 않으며, 어떤 것도 이를 디코딩하거나 파싱하지 않습니다. 이는 주체(principal)와 토큰을 담은 암호화된 블롭일 수 있으며, 불투명한 바이트를 비교하는 것이 검사의 전부입니다.

감지 범위를 벗어난 경우가 하나 있습니다. 서버가 requestState로 응답하고 inputRequests가 없을 때, 변조된 재시도는 어떤 것과도 일치하지 않고 아무 키에도 응답하지 않으므로, 이를 원래 요청과 연결할 것이 아무것도 남지 않습니다. 그리하여 그것은 위반이 아니라 무관한 호출로 읽힙니다.

다른 머신에서 감시하기

캡처는 트래픽이 발생하는 머신에 국한시키고 네트워크 구간에는 SSH를 사용하십시오. 그래야 mcpsnoop는 자체적인 원격 전송 수단이 필요 없습니다.

실시간 보기

워크스테이션에서 TUI를 실행하고 원격 머신의 mcpsnoop 소켓을 다시 그쪽으로 포워딩하십시오. 실시간 터널은 SSH Unix-socket 포워딩을 사용하므로 양쪽 모두 Linux 또는 macOS를 실행해야 합니다. Windows에서는 아래의 사후(post-mortem) 로그 복사를 사용하십시오.```bash

on your workstation, start the TUI

mcpsnoop

create the remote socket directory once

ssh remote-user@remote-host 'mkdir -p ~/.local/state/mcpsnoop'

print the tunnel command, then run the printed ssh -R line

mcpsnoop remote remote-user@remote-host

on the remote host, wrap your server as usual

mcpsnoop -- node build/index.js

소켓은 원격의 상태 디렉터리 아래에 있으며, `MCPSNOOP_HOME`,
다음으로 `XDG_STATE_HOME/mcpsnoop`, 다음으로 `~/.local/state/mcpsnoop` 순서로 확인됩니다. 기본적으로 mcpsnoop은
`user@host`에서 Linux 홈 `/home/<user>`를 가정하며, 해당 추측으로 대체될 때마다
stderr에 알림을 출력합니다. 원격이 다른 경로로 확인되면,
기본값이 아닌 해당 항목을 지정하세요.```bash
# a non-Linux or custom home, macOS is /Users/<user> and root is /root
mcpsnoop remote --remote-home /Users/remote-user remote-user@remote-host

# an explicit MCPSNOOP_HOME on the remote
mcpsnoop remote --remote-mcpsnoop-home /srv/mcpsnoop remote-user@remote-host

# an explicit XDG_STATE_HOME on the remote
mcpsnoop remote --remote-xdg-state-home /var/lib/state remote-user@remote-host

사후 분석

SSH를 통해 원격 세션을 TUI로 바로 스트리밍하세요. 로컬 복사본이 필요 없습니다.```bash ssh remote-user@remote-host 'cat ~/.local/state/mcpsnoop/sessions/session.jsonl' | mcpsnoop open -

대신 로컬 사본을 유지하려면, 로그를 세션 디렉토리로 scp하고
TUI를 평소처럼 실행하세요.```bash
# copy the remote logs into your local sessions directory
mkdir -p ~/.local/state/mcpsnoop/sessions
scp remote-user@remote-host:'~/.local/state/mcpsnoop/sessions/*.jsonl' \
  ~/.local/state/mcpsnoop/sessions/

# open the TUI, it backfills the copied sessions
mcpsnoop

보안

mcpsnoop은 사용자가 감싸는 서버 명령을 실행하므로, 신뢰하는 서버만 감싸고 신뢰하지 않는 서버는 컨테이너에서 실행하세요. 사용자가 클라이언트 구성에 넣지 않은 것은 절대 실행하지 않습니다.

캡처된 프레임에는 프롬프트, 도구 인수, 자격 증명 및 도구 결과가 포함될 수 있습니다. 페이로드에 비밀 정보가 포함될 수 있다면, 관찰된 트레이스 사본을 지우도록 redaction을 선택(opt in)할 수 있으며, 이때 프록시된 바이트는 변경 없이 그대로 통과합니다.

키 기반 redaction은 일치하는 JSON 객체 키 아래의 전체 값을 대체하며, 동일한 키 집합이 감싼 서버의 명령줄 인수에도 최선을 다해(best effort) 적용됩니다. 따라서 --api-key=sk-x--token sk-x--redact-secrets 사용 시 삭제됩니다. 인식 가능한 플래그 이름 없이 비밀 정보를 담은 인수는 감지할 수 없습니다.

경로 기반 redaction은 JSONPath 표현식으로 선택된 값만 대체합니다. 이는 공통 키 이름이 한 위치에서는 민감하지만 다른 위치에서는 안전한 경우에 유용합니다. --redact-path를 반복하면 둘 이상의 위치를 삭제할 수 있습니다.

값 기반 redaction은 관찰된 문자열 값, stderr 텍스트 및 JSON이 아닌 텍스트 프레임에 정규 표현식을 적용합니다.

세 가지 모두 최선 노력(best effort) 방식입니다. 정규 표현식은 비밀 정보를 놓치거나, 무해한 텍스트를 과도하게 일치시키거나, 변환되거나 인코딩된 값을 감지하지 못할 수 있습니다.

Redaction은 결코 비난으로 변하지 않습니다. 관찰된 한 항목을 다른 항목과 비교하는 모든 검사(라우팅 헤더와 본문, Mcp-Param 값과 그 값이 미러링하는 인수, 도구 스키마와 개정판이 해당 도구에 요구하는 사항)는 mcpsnoop가 바이트를 다시 쓴 쪽이 언제인지 알고 있으며, 사용자 자신의 개인 정보 보호 설정 때문에 서버를 보고하는 대신 침묵을 유지합니다. 도구 정의 드리프트는 예외이며 의도적으로 그렇습니다. redaction을 켜면 기록되는 내용과 그에 따라 기준(baseline)이 보유하는 내용이 변경되기 때문입니다. 도구 정의 드리프트 감지를 참조하세요.```bash

built-in preset of common secret keys

mcpsnoop --redact-secrets -- node build/index.js

or name your own keys

mcpsnoop --redact-key token,api_key,password -- node build/index.js

scrub one location without redacting every field named password

mcpsnoop --redact-path '$.params.arguments.password' -- node build/index.js

wildcards scrub every matching array element

mcpsnoop --redact-path '$.params.arguments.accounts[*].password' -- node build/index.js

scrub obvious token-shaped values outside known keys

mcpsnoop --redact-value 'sk-[A-Za-z0-9]+' -- node build/index.js

combine the layers in http mode

mcpsnoop http --target http://localhost:3000/mcp --redact-secrets --redact-value 'Bearer\s+\S+'

원격 워크플로우의 경우 SSH 터널링 또는 SSH 파일 전송을 사용하여 전송 인증,
암호화, 호스트 검증, 키 교체, 감사 정책이 기존 SSH 설정에 유지되도록
하십시오.

## 기여

이슈와 풀 리퀘스트를 환영합니다. 자세한 내용은 [CONTRIBUTING.md](https://github.com/kerlenton/mcpsnoop/blob/HEAD/CONTRIBUTING.md)를
참조하세요.

## 라이선스

[MIT](https://github.com/kerlenton/mcpsnoop/blob/HEAD/LICENSE)

카테고리