
MCP용 Wireshark. AI 클라이언트와 MCP 서버 간의 모든 실제 도구 호출을 터미널에서 실시간으로 보여주는 투명 프록시.
MCP용 Wireshark. AI 클라이언트와 MCP 서버 사이의 모든 실제 도구 호출을 터미널에서 실시간으로 보여주는 투명 프록시입니다.
공식 MCP Inspector는 자체 클라이언트로 연결되므로 사용자의 클라이언트(Cursor, Claude Code, Codex)가 서버에 실제로 보내는 내용을 볼 수 없습니다. 또한 요청이 도착하기를 기다리는 방식은 모델이 호출하지 않았거나 잘못된 인수로 호출한 경우를 표시할 수 없습니다. 도구가 조용히 호출되지 않거나, 기능이 일치하지 않거나, 호출이 그냥 멈추는 경우 로그를 뒤지고 추측할 수밖에 없습니다.
mcpsnoop는 대신 실제 데이터 경로에 위치합니다. 서버 명령을 mcpsnoop로 감싸면 실제 클라이언트와 서버가 통신하는 동안 모든 JSON-RPC 프레임을 실시간으로 볼 수 있습니다.
이 페이지는 mcpsnoop GitHub Action의 목록이기도 하므로 전체 내용을 여기에 담았습니다. 캡처된 세션을 검사하고, 모든 발견 사항을 코드 스캐닝 경고로 기록하며, 게이트로 설정한 조건에 따라 작업을 실패 처리합니다.```yaml permissions: security-events: write contents: read
steps:
원하는 릴리스를 고정하세요. 최신 버전은
[릴리스 페이지](https://github.com/kerlenton/mcpsnoop/releases)에 있습니다. 모든 입력,
종료 코드의 의미, 그리고 액션 없이 연결하는 방법은
아래의 [GitHub Action](#the-github-action)에 있습니다.
## 빠른 시작
설정할 것 없이 바로 확인해 보세요.```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은
파일을 복원하며, 더 이상 래핑된 서버가 없으면 백업을 제거합니다.
MCP 서버는 시작 시 한 번만 실행되므로, 두 작업 중 하나를 수행한 후에는
Claude Desktop을 다시 시작하세요.
그런 다음 평소처럼 클라이언트를 사용하고 UI를 엽니다.```bash mcpsnoop
플래그도, 소켓 경로도, 기억해야 할 시작 순서도 없습니다. 심(shim)과 UI는 서로를 자동으로 찾으며, UI는 디스크에서 과거 세션을 백필(backfill)합니다.
스트리밍 가능한 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 실행은 이 경우 실패한다.
자체 서버가 없다면? 직접 시도해 보기에서 공개된 테스트 서버를 대상으로 자신의 클라이언트를 구동해 볼 수 있다. 세션이 끝난 후 이를 검사하려면 로그에서 과거 세션 검토를 참조하라.
프로젝트 전반에 동일한 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
`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 inventory` | 이 머신에서 mcpsnoop을 통해 실행된 모든 서버 나열 |
| `mcpsnoop stats` | 저장된 모든 캡처를 서버 및 도구별로 한 행씩 접기 |
| `mcpsnoop prune` | 특정 기준일보다 오래된 저장된 세션 로그 삭제 |
| `mcpsnoop wrap <server>` | Claude Desktop의 서버 중 하나를 mcpsnoop을 통해 라우팅 |
| `mcpsnoop unwrap <server>` | 해당 서버의 항목을 원래 상태로 되돌리기 |
| `mcpsnoop remote <user@host>` | SSH 터널 명령 출력 |
| `mcpsnoop demo` | 스크립트된 세션 재생 |
전체 목록은 `mcpsnoop help`를, 특정 명령의 플래그는 `mcpsnoop help <command>`를 실행하세요.
## 비교
| | MCP Inspector | mcpsnoop |
|---|:---:|:---:|
| 실제 클라이언트 및 서버 트래픽을 볼 수 있음 | 아니요 | 예 |
| 응답 없는 호출 및 스트림 오류 플래그 | 아니요 | 예 |
| 스트림을 손상시키는 불필요한 출력 플래그 | 아니요 | 예 |
| 잘못된 JSON-RPC 프레임 플래그 | 아니요 | 예 |
| 승인 후 도구 정의 변경 감지 | 아니요 | 예 |
| 대화형 터미널 UI | 아니요 | 예 |
| 구성 불필요, 플래그나 순서 없음 | 아니요 | 예 |
| 기능 검사기 | 부분적 | 예 |
| 캡처된 호출 재생 | 아니요 | 예, stdio 및 HTTP를 통해 |
| 세션 내보내기 (json / html / text / otlp) | 아니요 | 예 |
| 단일 바이너리, 런타임 종속성 없음 | 아니요 | 예 |
## 설치
### npm
Go 툴체인이 필요 없습니다. 대부분의 MCP 서버는 Node 또는 Python으로 작성되므로
이것이 가장 빠른 방법입니다.```bash
npx mcpsnoop -- node build/index.js
The npm 패키지에는 자체 코드가 포함되어 있지 않습니다. 6개의 플랫폼 패키지가 각각 하나의 빌드를 담고 있으며, npm은 사용자 머신과 일치하는 단일 패키지만 설치하므로 설치 시 다운로드할 항목이 없고 프록시에서 차단할 항목도 없습니다. 매번 실행할 때마다 가져오는 대신 유지하려면 npm i -g mcpsnoop을 사용하세요.
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는 하나의 바이너리에서 두 가지 역할을 수행합니다. mcpsnoop -- <server>는 클라이언트가 실행하는 투명한 셰임(shim)으로, 바이트를 그대로 전달하면서 모든 프레임의 사본을 허브로 보냅니다. 인수 없이 실행하는 mcpsnoop는 바로 그 허브이자 실시간 TUI입니다. 둘은 잘 알려진 소켓과 디스크의 로그를 통해 연결되므로, 어느 쪽이 먼저 시작할 필요가 없습니다.
허브는 기본적으로 가장 최근의 저장된 세션 100개를 로드하여 시작 작업을 제한된 범위로 유지하면서도 오래된 트레이스를 삭제하지 않습니다. 다른 제한을 선택하려면 mcpsnoop --history-limit N을 사용하고, 전체 기록을 로드하려면 mcpsnoop --history-limit 0을 사용하세요. 오래된 세션은 mcpsnoop open <session-id> 및 mcpsnoop export <session-id>를 통해 계속 사용할 수 있습니다.
기록 제한은 로드되는 세션 수를 제한합니다. 세션 내부에서 실시간 TUI는 두 번 제한됩니다. 대화가 많은 서버를 계속 지켜보는 허브는 그대로 두면 종료될 때까지 커지기 때문입니다. 프레임 본문은 최대 64MiB까지만 유지하며 가장 오래된 것부터 해제하고, 프레임은 최대 200,000개까지만 유지하며 그 이후로는 가장 오래된 것을 완전히 버립니다. 첫 번째 제한은 대용량 페이로드를 캡처할 때 부딪히는 한계이고, 두 번째 제한은 작은 알림이 길게 이어지는 스트림에서 발생하는 한계입니다.
어떤 제한도 답을 바꾸지 않습니다. 본문이 해제된 프레임은 행, 판정, 타임라인에서의 위치를 유지하며, 검사기(inspector)는 빈 프레임을 표시하는 대신 본문이 사라졌다고 표시합니다. 완전히 버려진 프레임은 먼저 해당 도구 호출의 통계를 실행 합계에 반영하므로, 도구 요약과 서버가 컨텍스트에서 소모하는 비용은 세션이 수행한 모든 호출을 설명하며 최근 호출만 설명하지 않습니다. 스트림 하단에는 디스크에만 있는 오래된 프레임 수가 표시되며, r은 더 이상 보유하지 않는 매개변수가 있는 프레임은 다른 것을 재생하는 대신 거부합니다.
mcpsnoop open <session-id>는 로그를 읽고 전체를 보유하며, TUI에서 내보내기도 로그를 읽으므로 둘 다 제한되지 않습니다. check, export, diff는 의도적으로 무제한 저장소를 구축합니다. 대규모 캡처에서 과소 보고하는 게이트는 메모리를 사용하는 게이트보다 더 나쁘기 때문입니다.
기록 제한은 로드되는 내용을 제한합니다. mcpsnoop 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 duration을 허용합니다. 도구 기준선은
그대로 두는데, 기준선은 세션이 아닌 서버 라벨로 키가 지정되기 때문입니다.
Inspector처럼 옆에 있는 것이 아니라 실제 파이프 안에 있기 때문에
실제 클라이언트와 서버가 서로 주고받는 내용을 정확히 볼 수 있으며,
서버가 어떤 언어로 작성되었든 관계없습니다.
## 키 바인딩
| 키 | 동작 | | 키 | 동작 |
|---|---|---|---|---|
| `enter` | 검사 / 드릴다운 | | `/` | 필터 |
| `esc` | 뒤로 | | `:` | 명령 |
| `j` / `k` | 이동 | | `r` / `R` | 재생 / 편집 후 재생 |
| `g` / `G` | 맨 위 / 맨 아래 | | `c` | 기능(capabilities) |
| `ctrl-f` / `ctrl-b` | 페이지 | | `s` | 도구 요약 |
| `p` | 일시정지 | | `y` | 복사 |
| `shift`+`<키>` | 열 기준 정렬 | | `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)
The last one only finds anything on a server speaking 2025-11-25 or earlier. The 2026-07-28 revision removed server-initiated requests, and a server that needs something from the client now answers the client's own request asking for it, then the client retries. mcpsnoop links those retries back to the request they continue, so the exchange reads as one call rather than several.
Turn any captured session into a portable file.```bash mcpsnoop export -T json|html|text|har|otlp [-o file|-] [session-id|log.jsonl|-]
| 형식 | 제공 내용 |
|---|---|
| `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`는 입력과 같은 파일 이름을 출력으로 지정하는 것을 거부하며, 임시 파일을 통해 작성한 뒤 이름을 바꿔 제자리에 넣으므로, 실행이 실패해도 이전 파일은 온전하게 남습니다.
도구의 `inputSchema`와 `outputSchema`는 `tools/list` 결과에 광고된 대로, `--redact-key`와 `--redact-secrets`의 영향을 받지 않습니다. 그 이유는 세 가지입니다.
- 스키마 안의 이름은 값이 아니라 타입 선언입니다.
- 그 이름 자체는 어느 쪽이든 로그에 남습니다.
- `token`이라는 속성 아래의 하위 스키마를 지우면 도구 자체의 검사도 함께 사라집니다.
예외는 바로 그 위치뿐이므로, 우연히 `inputSchema`라고 불리는 인자는 다른 인자와 마찬가지로 지워지며, 데이터가 아닌 구조를 담는 `default`, `const`, `examples`, `enum`에서 중단됩니다. `--redact-path`를 사용해 스키마 내부의 무언가를 지정하거나, `--redact-value`를 사용하세요. 이는 mcpsnoop이 파싱하는 두 키워드인 `type`과 `x-mcp-header`를 제외한 어디에 있든 텍스트와 일치합니다.
각 플래그가 도달하는 범위는 서로 다르므로, 가정하지 말고 결과를 확인하세요. 네 가지 모두 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 트래픽을 차단하지 않습니다. 수집기(collector)를 사용할 수 없는 경우 mcpsnoop는 백그라운드에서 재시도하며, 바운드 큐가 가득 차면 새 추적 프레임을 버립니다. 일반 JSONL 세션 로그는 영구 기록으로 유지됩니다.
ID 또는 JSONL 경로로 저장된 두 세션을 비교합니다.```bash mcpsnoop diff before-session after-session mcpsnoop diff old.jsonl new.jsonl
보고서에는 추가되거나 제거된 도구, 설명 및 `inputSchema` 변경 사항, 상태가 변경된 일치하는 도구 호출, 그리고 주목할 만한 지속 시간 변화가 표시됩니다. 호출은 도구 이름과 인수로 일치하므로 호출 순서가 바뀌어도 올바르게 비교됩니다. 기본적으로 지속 시간 변화는 최소 100ms 및 2배 이상 차이가 나야 합니다. `--duration-threshold` 및 `--duration-ratio`를 사용하여 해당 기준을 조정할 수 있습니다.
`--exit-code`를 전달하여 회귀에 대해 CI를 게이트할 수 있습니다. after 세션에서 다음이 발생하면 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, invalid, warn는 자체적으로 검사를 실패시킵니다. 나머지는 선택(opt-in) 항목입니다.
쉼표로 구분된 하위 집합을 전달하여 작업이 관심을 두는 항목에만 게이트를 걸거나, 세션을 생략하여 가장 최근 캡처를 확인하거나, -를 사용하여 stdin에서 JSONL을 읽을 수 있습니다.
| 신호 | 실패 조건 |
|---|---|
error | JSON-RPC 오류로 응답된 호출, isError로 표시된 결과, 또는 실패로 종료된 작업 |
invalid | 프로토콜 채널에서 유효한 JSON-RPC가 아닌 프레임, 일반적으로 서버가 stdout에 로그를 기록하는 경우 |
warn | MCP 또는 JSON-RPC 사양이 설정한 기대치를 위반하는 프레임 |
mismatch | 본문과 일치하지 않는 라우팅 헤더, 배치에 편승하거나, 개정판이 요구하는 곳에서 누락된 경우 |
pending | 캡처가 종료될 때 아직 열려 있는 요청으로, 호출자가 대기 상태로 남겨진 경우 |
late-result | 요청이 취소된 후 도착한 응답 |
drift | 기준선이 승인된 후 광고된 도구 정의가 변경된 경우 |
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
종료 코드는 두 가지 상황 중 어떤 것이 발생했는지를 알려주며, CI 래퍼는 그 차이를 구분해야 합니다. 1은 검사가 실행되었고 게이트를 통과하지 못한 무언가가 실패했음을 의미하므로, 발견 사항은 실제이며 게시할 가치가 있습니다. 2는 검사가 전혀 실행되지 않았음을 의미합니다: 존재하지 않는 경로, 세션 로그가 아닌 파일, 아무것도 담고 있지 않은 상태 디렉터리, 구문 분석되지 않는 플래그 등이 그 예입니다. 2의 경우 stdout에 아무것도 기록되지 않으므로, 파이프라인은 빈 보고서를 마치 평결인 것처럼 업로드하지 않습니다.
신호 개수 외에도 실행 형태를 검증합니다. 이러한 옵션은 서로 및 --fail-on과 함께 구성되며, 어떤 실패든 종료 코드 1(검사가 실행되어 무언가를 발견했음을 의미하는 코드)로 종료됩니다.
| 플래그 | 실패 조건 |
|---|---|
--max-duration <dur> | 완료된 도구 호출 중 하나 이상이 예산을 초과한 경우, 해당 횟수와 최악의 호출을 보고 |
--expect-tool <name> | 지정된 도구가 한 번도 호출되지 않은 경우 (반복 가능) |
--forbid-tool <name> | 지정된 도구가 호출된 경우 (반복 가능) |
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 수준으로 보고되므로 보고서와 게이트가 결코 어긋나지 않습니다.
결과는 발견 항목이 나온 로그를 가리키며, 그 방식은 로그가 읽힌 위치에 따라 달라집니다.
file:// URI가 됩니다.경고는 해당 경로가 분석된 커밋의 파일인 경우에만 주변 줄과 함께 렌더링되므로, 워크플로가 artifacts/에 생성한 캡처는 메시지, 규칙, 줄 번호를 담은 경고를 열지만 소스 보기는 없습니다. 전체 렌더링을 원하는 캡처를 커밋하는 것이 유일한 방법입니다.
코드 스캐닝은 실행에 25,000개 이상의 결과가 있는 파일을 거부하고, 수락한 결과 중 상위 5,000개만 표시하므로 보고서는 5,000개로 제한됩니다. 먼저 게이트가 실패한 발견 항목이 나오고, 그다음에 몇 개가 제외되었는지 알려주는 mcpsnoop/report-truncated 결과가 나옵니다. 텍스트 및 junit 형식은 완전하게 유지됩니다.
아래의 모든 내용은 이 액션이 대신 수행하는 작업입니다. mcpsnoop를 설치하고, 캡처를 확인하며, 발견 항목을 Security 탭에 등록하고, 게이트로 지정한 항목에 대해 작업을 실패시킵니다.```yaml permissions: security-events: write contents: read
steps:
릴리스를 고정하세요. 원하는 버전이면 됩니다. 최신 버전은
[릴리스 페이지](https://github.com/kerlenton/mcpsnoop/releases)에 있습니다. 의도적으로
떠다니는 `v1`은 없습니다. 고정된 릴리스는 액션이 설치하는 바이너리이기도 하므로,
둘이 어긋날 수 없고, 낡아버릴 버전 기본값도 없습니다.
| 입력 | |
|---|---|
| `session` | 확인할 `.jsonl` 캡처 파일. 저장소 루트 기준 상대 경로. 필수 |
| `fail-on` | `--fail-on`과 동일. CLI 기본값과 동일한 값으로 기본 설정됨 |
| `args` | 명령줄에서 따옴표로 묶은 기타 `check` 플래그. 액션이 리포트를 읽으므로 `--format`은 거부됨 |
| `upload-sarif` | 리포트를 코드 스캐닝으로 전송. `true` |
| `category` | 코드 스캐닝 네임스페이스. `mcpsnoop`. 매트릭스의 각 레그마다 다르게 지정하세요. 그렇지 않으면 레그들이 서로 덮어씁니다 |
| `fail-on-findings` | 발견 사항이 있으면 작업을 실패 처리. `true`. `false`로 설정하면 알림을 등록하고 코드 스캐닝의 필수 검사가 결정하도록 둡니다 |
| `version` | 설치할 mcpsnoop 버전. 고정한 릴리스로 기본 설정됨 |
| `install` | mcpsnoop가 이미 PATH에 있으면 `false`. 릴리스가 빌드되지 않은 플랫폼에서 사용하는 방식입니다 |
출력은 `outcome`, `sarif`, `exit-code`입니다. `outcome`은 `passed`,
`findings`, 또는 `error`이며, 세 번째는 별도로 처리할 가치가 있습니다. 이는
아무것도 검사되지 않았음을 의미하며, 아무것도 발견되지 않은 것과는 다릅니다. **검사를
수행할 수 없는 실행은 `fail-on-findings` 값과 관계없이 작업을 실패 처리합니다**. 왜냐하면
아무것도 검증하지 않고 초록불이 켜지는 파이프라인은 실패하는 것보다 더 나쁘기 때문입니다.
작업에는 `security-events: write` 권한이 필요합니다. 그렇지 않으면 업로드가 403을 반환합니다.
코드 스캐닝이 없는 저장소에서는 `upload-sarif: false`로 설정하세요.
### 직접 연결하기
이 액션은 네 단계로 이루어져 있으며 마법은 없습니다. 수동으로 수행하려면
그만큼의 주의가 필요합니다. 업로드는 리포트가 있는 실행에서 실행되어야 하며,
이는 종료 코드 0 또는 1로 종료된 실행이고 종료 코드 2로 종료된 실행은 아닙니다. 그리고
작업을 실패 처리하는 단계는 그 뒤에 와야 합니다. 그렇지 않으면 발견 사항이
존재 목적인 탭에 도달하지 못합니다.```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
id: check
run: |
code=0
mcpsnoop check --format sarif artifacts/session.jsonl > mcpsnoop.sarif || code=$?
echo "exit-code=$code" >> "$GITHUB_OUTPUT"
# 2 means the check never happened, so there is no report to publish and
# nothing was verified. Stop here rather than uploading an empty file.
[ "$code" -le 1 ] || exit 1
- name: Upload mcpsnoop SARIF report
if: ${{ !cancelled() }}
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: mcpsnoop.sarif
category: mcpsnoop
- name: Fail on findings
# Separate, and after the upload, so the findings reach the Security tab on
# exactly the runs that have some.
if: ${{ !cancelled() && steps.check.outputs.exit-code == '1' }}
run: exit 1
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이 직접 삭제한 값은 불일치로 보고되지 않습니다.
위의 라우팅 헤더는 프레임이 전달하는 유일한 헤더였으므로, Streamable HTTP 전송의 나머지 필수 헤더는 이를 확인할 수 있는 대상에 도달하지 못했습니다. Content-Type이 가장 예리한 사례였습니다. 응답 측에서는 이미 이를 읽어 SSE 스트림과 JSON 본문을 구분한 뒤 버렸습니다.
이제 HTTP 프레임은 전송이 규칙을 명시하는 헤더를 전달하며, 그중 두 가지 규칙을 확인할 수 있습니다.
| 규칙 | 보고 방식 |
|---|---|
클라이언트는 application/json과 text/event-stream을 모두 나열하는 Accept를 전송해야 함(MUST) | 요청에 대한 warn |
JSON-RPC 요청에 응답하는 서버는 Content-Type: application/json 또는 text/event-stream을 반환해야 함(MUST) | 응답에 대한 warn |
두 문장 모두 2025-11-25와 2026-07-28에서 동일하게 읽히므로, 드리프트(drift) 및 확장 검사와 달리 개정 게이트가 필요 없습니다. Origin도 기록됩니다. 서버는 이를 반드시 검증해야 하고 유효하지 않을 때 반드시 403으로 응답해야 하기 때문이지만, mcpsnoop은 허용된 출처를 알 수 없으므로 값을 판단하지 않고 표시만 합니다.
와일드카드도 집계됩니다. */*를 보내는 클라이언트는 두 유형을 모두 제공한 것이므로 보고되지 않으며, Content-Type의 charset 매개변수는 무시됩니다. mcpsnoop이 이러한 헤더를 기록하기 전에 캡처된 로그는 모든 프레임을 기록되지 않은 헤더로 보고하는 대신 조용히 유지되며, stdio에는 이러한 헤더가 전혀 없습니다.
Authorization은 의도적으로 캡처되지 않습니다. 챌린지를 토큰 사실로 바꾸는 것은 그 자체로 문제이며, 베어러 토큰을 디스크에 저장하는 것은 그 해결책이 아닙니다. Mcp-Session-Id와 Last-Event-ID도 캡처되지 않습니다. 2026-07-28 개정판은 둘 다 제거하고 서버에 이를 무시하라고 지시하므로, 확인할 규칙이 남아 있지 않습니다.
서버 라벨에 대해 관찰된 첫 번째 완전한 tools/list가 신뢰할 수 있는 기준선(baseline)이 됩니다. 이후 세션은 해당 기준선을 필드별로 비교합니다:
추가되거나 제거된 도구도 비교되며, 이는 필드 비교가 아닌 집합 비교입니다.
주석이 가장 중요합니다. readOnlyHint로 승인된 도구가 나중에 자신을 파괴적이라고 선언하는 것이 이 검사가 존재하는 이유이며, 사양은 클라이언트에게 주석을 신뢰할 수 없는 것으로 취급하라고 지시합니다. 제목과 아이콘은 사용자가 보는 것이므로 추적되며, 사양은 도구의 title을 annotations.title 및 이름보다 우선시합니다. 세션 테이블과 도구 요약은 MCP 트래픽을 차단하거나 변경하지 않고 드리프트를 플래그로 표시합니다.
주석은 사양 기본값을 통해 비교되므로, 이미 의존하고 있던 힌트를 명시적으로 표기하기 시작한 서버는 보고되지 않습니다. mcpsnoop이 필드를 추적하기 전에 기록된 기준선은 기록하는 필드에 대해서는 계속 작동하며, 답할 수 없는 필드가 무엇인지 명시합니다. 현재 정의를 신뢰하게 되면 mcpsnoop baseline --accept로 다시 기록하세요.
편집(redaction) 기록 방식을 변경하면 드리프트 비교 대상도 변경됩니다. --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)을 검증하는 대신 기록하게 됩니다. **드리프트(drift) 시 실패를 요청한 실행이 아무것도 검증하지 않았다면 통과하지 않으며**, 어떤 디렉터리를 유지해야 하는지 알려줍니다. 기준선 기록이 실패로 간주되는 유일한 경우가 바로 이것입니다. `--fail-on`에 `drift`가 없으면 기준선 기록은 평소와 다름없는 작업이며 종료 코드를 변경하지 않습니다.
따라서 드리프트 게이트(drift gate)가 의미를 가지려면 기준선이 실행 간에 유지되어야 합니다. `--baseline`을 체크인되거나 캐시된 디렉터리로 지정하거나, `MCPSNOOP_HOME`을 유지되는 경로로 설정하세요.```
recorded first-seen tool baseline (trusted, not verified)
check failed: drift
I'm ready to translate the Kitploit tool content from English to Korean. Please provide the Markdown content you'd like me to translate.```bash mcpsnoop check --fail-on drift --baseline .mcpsnoop/baselines session.jsonl
`drift`는 `check`에 대해 옵트인(opt-in)입니다. 기본 `error,invalid,warn` 게이트는 변경되지 않습니다.
### 어느 쪽도 협상하지 않은 기능 포착하기
SEP-2133은 선택적 기능을 핵심 프로토콜에서 확장으로 옮겼으며, 각 측의 기능(capabilities)에 있는 `extensions` 맵에 광고됩니다. Tasks도 그중 하나이므로, 2026-07-28 기준으로 `tasks/get`, `notifications/tasks` 또는 `tools/call`이 작업 핸들로 응답하는 것은 상대방이 Tasks를 지원한다고 밝힌 경우에만 의미가 있습니다.
상대방이 지원하지 않을 때, 사양은 명확합니다: 지원하는 측은 핵심 동작으로 폴백(fallback)하거나 요청을 거부해야 합니다. 그렇게 하지 않는 것이 기능이 연결된 것처럼 보이다가 조용히 아무 일도 하지 않는 이유이며, 대신 독자가 받는 것은 몇 프레임 뒤의 `-32601` 또는 `-32021`, 또는 결코 진행되지 않는 작업입니다. mcpsnoop은 확장에 접근한 프레임에서 경고하며 어느 쪽이 이를 광고하지 않았는지 명명합니다.```
tool "slow" answered with a task handle uses the io.modelcontextprotocol/tasks
extension, which the client never advertised
warn이므로 기본 check 실행은 이에 대해 실패합니다. 캡처가 협상된 내용을 보여줄 수 없을 때(핸드셰이크 이후에 시작된 캡처 또는 자체 편집 기능이 기능을 삭제한 캡처)와 2026-07-28 이전 개정판에서는 조용히 넘어갑니다. 해당 개정판에서는 tasks/*가 핵심 프로토콜이므로 이를 사용하는 것이 올바릅니다.
2026-07-28 개정판은 Roots, Sampling, Logging을 더 이상 사용하지 않습니다. 이들은 최소 1년 동안 계속 작동하므로 mcpsnoop은 이를 오류로 처리하지 않고 표시만 합니다. 스트림, 기능 검사기, 내보내기 모두 이를 플래그로 표시하며, 각 표시는 대체 기능을 명명합니다.
세 가지 중 두 가지는 이제 다중 왕복 요청을 통해서만 접근할 수 있으며, 메서드 이름은 프레임 자체가 아닌 서버의 inputRequests 맵 안에 있습니다. 이러한 항목도 플래그로 표시되므로 새 패턴으로 전환한 서버가 조용히 보고를 중단하지 않습니다.```bash
mcpsnoop check --fail-on deprecated session.jsonl
`drift`와 마찬가지로 `deprecated`는 옵트인(opt-in) 방식입니다. 기본 실행에서는 개수만 보고하고 녹색으로 유지되므로, 아직 합법적인 deprecated 기능을 사용하는 세션은 그 자체로 CI를 빨간색으로 만들지 않습니다.
### 클라이언트가 처리에 어려움을 겪는 플래그 스키마 구조
서버가 완전히 유효하더라도 에이전트가 사용하기 어려울 수 있습니다. 클라이언트마다 JSON Schema를 실제로 지원하는 정도가 다르며, 모델이 계속 잘못 호출하는 도구는 종종 스키마가 클라이언트가 제공하는 것보다 더 많은 것을 요구한 경우입니다.
`s`로 여는 도구 요약에는 SCHEMA 열이 있으며, 각 광고된 도구의 스키마에서 가장 눈에 띄는 특징을 표시하고, 종류가 두 개 이상이면 끝에 `+`를 붙입니다.
| 표시 | 의미 |
|---|---|
| `no root` | `inputSchema`가 없거나, JSON 객체가 아니거나, 루트 유형이 `"object"`가 아님 |
| `dialect` | 개정판이 기본값으로 사용하는 2020-12가 아닌 다른 방언을 지정하는 `$schema` |
| `ext ref` | 문서 외부를 가리키는 `$ref`로, 사양이 구현자에게 맹목적으로 따르지 말라고 경고하는 경우이기도 함 |
| `oneOf`, `anyOf`, `allOf`, `not` | 클라이언트마다 일관되지 않게 처리되는 합성(composition) 키워드 |
| `ref` | 같은 문서 내부를 가리키는 `$ref` |
| `untyped` | 유형을 선언하지 않고 수용 대상을 설명하는 다른 방법도 없는 속성 |
첫 번째를 제외한 모든 항목은 판정이 아닌 관찰입니다. `oneOf`를 사용하는 스키마는 잘못된 것이 아니라 클라이언트마다 다르게 읽힐 가능성이 높을 뿐이며, 스키마는 원하는 방언을 선언할 수 있습니다. `no root`는 예외입니다. `Tool` 정의는 `inputSchema`를 요구하고 루트 유형을 `"object"`로 고정하므로, 목록을 검증하는 클라이언트는 해당 도구를 완전히 거부하고 호출 가능 상태가 되지 않으며, 그 이유를 설명하는 내용이 통신선에 실리지 않습니다. `no root`가 열의 맨 앞에 오는 이유가 바로 이것이며, mcpsnoop 자체의 편집(redaction)이 지운 스키마는 보고되지 않습니다. 읽을 수 없는 스키마는 잘못된 스키마가 아니기 때문입니다.
이 구분이 `check`가 이들을 처리하는 방식을 결정합니다. `no root`는 `tools/list` 프레임에 대한 경고이므로, 플래그 없이도 기본 `error,invalid,warn` 게이트를 실패시킵니다. 이것이 핵심입니다. 사용할 수 없는 도구를 제공하는 서버는 모든 핸드셰이크에 정상적으로 응답하고 단순히 `tools/call`을 받지 않을 뿐입니다. 관찰 항목은 `schema_findings`로 집계되어 `schema findings:` 아래에 보고되며, `--fail-on`에 `schema`를 추가할 때만 실행을 실패시킵니다. 둘 다 `--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는 그 형태만으로 인식되며,
그것이 가리키는 스키마는 절대 읽히지 않습니다.
r은 캡처된 호출을 라이브 서버에 대해 다시 실행합니다. stdio 캡처의 경우
명령이 로그에 있으므로 mcpsnoop는 격리된 복사본을 실행하고 해당 복사본에
요청을 보냅니다. HTTP 캡처에는 실행할 명령이 없으며, 기록된 엔드포인트는
사용자 정보와 모든 쿼리 값이 제거된 상태이므로, 다이얼할 주소가 아니라
서버를 식별하는 이름 역할을 합니다.
따라서 재생이 어디로 가는지 지정하면, mcpsnoop는 누군가 키를 눌렀다고 해서
프로덕션 엔드포인트로 다이얼하지 않습니다.```bash
mcpsnoop open --replay-target https://api.example.com/mcp session.jsonl
mcpsnoop open --replay-target https://api.example.com/mcp
--replay-header 'Authorization: Bearer sk-…' session.jsonl
`--replay-target` 없이 HTTP 세션은 작동할 수 없는 키를 제공하는 대신 그렇게 말합니다. 키가 있으면 `r`은 세션의 첫 전송 전에 여전히 물어보며, 기록된 명령이 실행되기 전에 답변을 받는 것과 같은 방식입니다.
자격 증명은 `--replay-header`를 통해서만 서버에 도달하며 다른 경로는 없습니다. mcpsnoop는 `Authorization` 헤더를 기록하지도 재생하지도 않으므로, 재생이 유출할 수 있는 캡처된 내용이 없습니다.
재생된 POST는 전송이 필수로 만드는 것을 담고 있으며, 캡처된 본문만으로는 담지 못하는 것들입니다: `MCP-Protocol-Version`, `application/json`과 `text/event-stream`을 모두 나열하는 `Accept`, `Mcp-Method`, 사양이 요구하는 경우 `Mcp-Name`, 그리고 캡처된 모든 `Mcp-Param-*`. 이들은 캡처에서 그대로 재전송되며, base64 센티널까지 포함하므로, 재파생이 할 수 있는 것처럼 본문과 불일치할 수 없습니다. 복사되지 않는 유일한 헤더는 프로토콜 버전인데, 재생된 본문이 mcpsnoop가 사용하는 개정판을 선언하고 헤더가 본문과 일치해야 하기 때문입니다.
`Mcp-Name`은 복사되는 대신 전송되는 본문에서 파생되는데, 사양이 이를 `params.name` 또는 `params.uri`에서 가져오고 서버가 본문과 불일치하는 헤더를 거부하도록 요구하므로, 도구 이름을 바꾸는 편집은 그렇지 않으면 이전 이름을 보내게 됩니다. `Mcp-Param-*` 헤더는 캡처된 인수를 반영하므로, 편집된 재생은 누군가 다시 작성한 본문에 대해 주장하는 대신 아무것도 보내지 않습니다. 캡처는 해당 계열의 헤더만 설정할 수 있습니다. 로그는 사람들이 주고받는 파일이며, 임의의 헤더를 지정하도록 허용하면 필수 헤더를 덮어쓰거나 아무도 전달하지 않은 자격 증명을 추가할 수 있습니다.
교정 규칙이 지운 `Mcp-Param-*`는 이유와 함께 재생을 중지합니다. 플레이스홀더를 보내면 mcpsnoop 자체의 바이트가 사용자가 입력한 것처럼 라이브 서버에 전달됩니다.
리다이렉트는 따르지 않고 거부됩니다. 주소는 사용자가 지정하고 답변한 것이며, 307을 따르면 그 선택이 원격 측에 넘어가 본문을 다시 보내고, 포트만 변경하는 홉에서는 자격 증명도 다시 보냅니다. mcpsnoop는 서버가 보내려던 위치를 보고하고, 대신 그 주소를 지정할지 여부를 사용자가 결정하게 합니다.
단일 JSON 객체로 도착하는 답변과 이벤트 스트림으로 도착하는 답변은 모두 읽히며, 실패는 번호가 아닌 이름으로 보고됩니다:
- 401은 서버가 요구한 스킴을 보고합니다
- `-32020`은 서버가 이의를 제기한 내용을 보고합니다
- JSON-RPC가 아닌 400 또는 404는 해당 주소가 이 개정판의 Streamable HTTP 엔드포인트가 아님을 말합니다
### 서버의 지연 시간을 사용자의 것과 구분하기
다중 왕복 요청에서 하나의 도구 호출은 여러 요청이며, 사람이 일러두기에 답변하는 데 쓴 초는 그 범위 안에 있습니다. 이는 의도적인데, 그 간격이 보통 가장 보고 싶은 것이기 때문이지만, 하나의 숫자로 두 질문에 모두 답할 수는 없음을 의미합니다.
서버가 1.2초 동안 작업하고 사용자가 37초를 쓴 `book_flight` 체인에서 `check --max-duration 5s`는 도구가 38.2초를 쓴 것으로 비난합니다. 여전히 그렇게 하는데, 그 플래그의 의미를 바꾸면 이미 설정한 모든 파이프라인이 느슨해지기 때문입니다. 두 형제 플래그는 대신 측정하는 것을 이름으로 지정합니다.```bash
mcpsnoop check --max-server-duration 1s session.jsonl # the server's share alone
mcpsnoop check --max-round-trips 2 session.jsonl # how chatty a tool is
git clone https://github.com/yourusername/yourproject.git
cd yourproject
pip install -r requirements.txt
python main.py --help
MIT``` assertion failed: 1 tool call exceeded the 1s server budget (worst: tool "book_flight" held for 1.2s) assertion failed: 1 tool call exceeded the 2 round trip budget (worst: tool "book_flight" took 3)
둘 다 기본적으로 꺼져 있어서 기본 `check` 실행에는 영향을 주지 않으며, 둘 다 프레임 타임스탬프와 mcpsnoop가 이미 추론한 링크에서 읽어오므로 의도를 추측하지 않습니다.
TUI에서 `i`를 누르면 세부 내역을 볼 수 있고, json, text, html 내보내기에서 `interactions`를 읽을 수도 있습니다. 각 항목은 하나의 논리적 작업으로, 왕복 횟수, 총 시간, 서버가 보유한 비율, 클라이언트를 기다린 비율을 포함하며, 각 홉에 대해 각 응답이 무엇을 요청했는지 알려주는 줄이 있습니다. 도구별 요약에는 `TRIPS` 열이 추가되어 아무것도 열지 않아도 말이 많은 도구를 확인할 수 있습니다.
`export --format har`는 서버의 비율을 `wait`에, 나머지를 `blocked`에 넣는데, 이는 해당 필드의 용도이므로 뷰어가 실제로 발생하지 않은 38초 서버 대기를 그리지 않게 됩니다.
횟수와 두 비율은 요청 시 파생되는 것이 아니라 프레임이 도착할 때 누적됩니다. 라이브 저장소는 예산 안에 머물기 위해 오래된 프레임을 해제하므로, 파생된 답변은 조용히 체인이 아닌 창(window)이 되기 때문입니다. 홉별 세부 내역은 여전히 보유 중인 프레임에서 읽어오며, 일부만 포함된 경우 그렇게 명시합니다. `ServerTime + ClientTurnaround`는 누구도 신뢰해야 하는 산술이 아니라 구조적으로 총 시간과 같습니다.
`--max-round-trips`는 아직 실행 중인 체인을 판단합니다. 이미 만든 모든 요청은 셀 수 있고, 서버가 계속해서 다시 요청하면 정확히 아무도 끝내지 못하는 작업이 생성되기 때문입니다. `--max-server-duration`은 종료를 기다리는데, 이는 `--max-duration`이 이미 적용하는 규칙입니다. 아직 열려 있는 작업은 판단할 대기 시간이 없기 때문입니다.
mcpsnoop가 연결할 수 없었던 작업은 자체 단일 홉 항목으로 유지됩니다. `matchRetry`는 의도적으로 모호한 링크를 거부하며, 이 보기는 그 공백을 메우지 않습니다.
요청 하나만 걸린 작업은 홉별 세부 내역을 포함하지 않습니다. 단일 홉은 위의 총 시간을 그대로 반복하기 때문입니다. 체인은 요청당 하나의 홉을 보고하며, 저장소가 더 이상 모든 프레임을 보유하지 않거나 작업이 홉을 구성하는 요청-응답 쌍 밖에서 처리되었을 때(작업 핸들이 그런 경우) 그렇게 명시합니다.
### 서버가 사용자에게 무엇을 요청했는지 확인
Elicitation은 MCP에서 사람이 서버에 데이터를 입력하는 유일한 경로이며, MRTR에서는 질문과 답변이 더 이상 하나의 교환의 두 절반이 아닙니다. 질문은 `InputRequiredResult`에 묻혀 있고, 답변은 다른 id의 재시도에서 `inputResponses` 안으로 돌아오며, 둘을 묶는 유일한 것은 mcpsnoop가 이미 추론한 링크입니다.
그 연결이 없으면 거부된 비밀번호 요청은 평범한 도구 오류로 읽힙니다.```
tools/call login_legacy [form] creds: decline after 3s
password string
TUI에서 l을 누르거나 json, text, html 내보내기에서 elicitations를 읽으세요.
각 행은 질문이 중단시킨 작업, 모드, 메시지, 요청된 내용, 사용자가 수행한 작업, 그리고 소요 시간을 나타냅니다. 재시도가 전혀 응답하지 않은 질문은 pending으로 표시되며, MRTR은 사양이 서버에게 클라이언트가 재시도할 것이라고 가정하지 말라고 지시하므로 이를 오류가 아닌 일반적인 결과로 처리합니다.
양식 행은 requestedSchema 속성 이름과 선언된 유형을 나열합니다. 교정 규칙이 하위 스키마를 대체한 속성은 자리 표시자가 서버가 선언한 것이 아니므로 자리 표시자 대신 알 수 없는 유형으로 표시됩니다. URL 행은 주소 전체를 담고 있으며, 사양은 클라이언트가 동의 전에 이를 표시하도록 요구하고, 호스트를 별도로 명명하여 서브도메인 스푸핑에 대비해 강조하라고 지시합니다.
원장은 제출된 값을 절대 담지 않습니다. 사용자가 입력한 내용은 필요한 사람을 위해 캡처에 남아 있으며, 내보내고 붙여넣기 위해 만들어진 요약 표면에서 이를 제외하는 것이 이 데이터를 교정 이야기에서 완전히 벗어나게 하는 이유입니다. 이는 사양이 의도적으로 자격 증명을 넣는 url 모드에서 가장 중요합니다.
재시도는 발행된 라운드에만 응답하며 다른 라운드에는 응답하지 않습니다. MRTR은 클라이언트가 요청된 일부를 생략할 때 서버가 새 라운드에서 다시 요청해야 한다고 알려주므로, 응답된 키 옆에 응답되지 않은 키 하나를 보유한 이전 라운드는 일반적인 트래픽이며, 응답되지 않은 절반은 이후 라운드의 답변을 빌리지 않고 pending 상태로 유지됩니다.
기록된 질문 하나는 제한되어 있습니다. 메시지, URL, 필드 목록은 본문을 해제하는 프레임 예산 밖에서 세션 수명 동안 유지되므로, 서버가 임의로 비용이 많이 들게 만들 수 없습니다. 한도는 실제 질문보다 훨씬 높으며, 잘린 메시지는 잘렸다고 표시됩니다.
여기서 경고하는 것은 없으며 check 종료 코드를 변경하는 것도 없습니다. 원장은 발생한 일을 기록합니다. 판단하지 않습니다.
check는 세션 하나를 읽고 diff는 정확히 두 개를 읽으므로, 가끔 실패하는 도구는 누군가 캡처를 직접 열기 전까지는 보이지 않습니다. run_query가 약 4분의 1의 시간 동안 isError로 응답하는 서버의 캡처 16개를 살펴보면, check는 가장 최근 것을 정직하게 깨끗하다고 보고합니다.```bash
mcpsnoop stats
mcpsnoop stats --since 7d --label prod
mcpsnoop stats --limit 20 --format json
# 4.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.```
read 16 logs of 16 in ~/.local/state/mcpsnoop/sessions
SERVER TOOL CALLS ERR PROTO FAIL% SESS p50 p95 p99 DEF
flaky-demo run_query 13 3 0 23.1% 3/13 434ms 519ms 519ms 195B
docs-mirror run_query 3 1 0 33.3% 1/3 357ms 434ms 434ms 195B
docs-mirror search_docs 12 0 0 0.0% 0/3 377ms 386ms 386ms 200B
flaky-demo search_docs 52 0 0 0.0% 0/13 42ms 58ms 59ms 200B
ERR와 PROTO는 사양에서 별개의 항목으로 정의하므로 별도의 열입니다. isError로 응답하는 도구는 모델이 조치를 취하고 재시도할 수 있는 무언가를 보고하는 것입니다. JSON-RPC 오류는 요청 또는 서버가 잘못된 경우입니다. SESS는 도구를 호출한 세션 중 실패를 본 세션 수로, "열 번 중 한 번"이라는 질문에 대한 답이며 호출 횟수 기준 비율로는 답할 수 없습니다.
행은 서버와 레이블을 함께 기준으로 삼습니다. 서버는 stdio의 경우 기록된 명령과 작업 디렉터리, HTTP의 경우 엔드포인트로, inventory가 사용하는 것과 동일한 식별자입니다. 어느 한쪽만 사용하면 합치면 안 되는 것을 합치게 됩니다. 레이블만 사용하면 하나의 이름을 파생하는 두 서버가 병합되는데, 이는 프로젝트의 두 체크아웃이 동일한 진입점을 실행할 때마다 발생합니다. 식별자만 사용하면 의도적으로 prod로 실행한 명령과 staging으로 실행한 명령이 병합됩니다. 두 실수 모두 깨끗한 두 분포를 어느 쪽도 설명하지 못하는 하나로 흐릿하게 만듭니다.
두 행이 실제로 레이블을 공유할 때 SERVER 셀에는 이를 구분하는 작업 디렉터리 또는 엔드포인트가 들어가고, JSON에는 모든 행에 command, cwd, endpoint가 포함됩니다. 모호했던 적이 없는 이름은 그대로 두므로 일반적인 표는 변경되지 않습니다.
로그의 모든 세션이 접혀서 합산되며 첫 번째 세션만이 아닙니다. 따라서 캡처를 연결해 만든 파일은 모두를 집계합니다.
백분위수는 원시 지속 시간을 풀링하여 계산합니다. 중앙값들의 중앙값은 아무것도 아닌 중앙값입니다. 다중 왕복 작업 하나는 요청이 몇 번이든 하나의 호출이자 하나의 지속 시간이며, 아직 열려 있는 호출은 지연 시간에 기여하지 않으면서 CALLS에는 포함됩니다.
한 번에 하나의 캡처만 상주합니다. 로그가 로드되어 실행 중인 카운터에 접힌 다음, 다음 로그가 열리기 전에 버려집니다. 따라서 수백 개의 디렉터리는 합계가 아닌 가장 큰 단일 캡처만큼만 비용이 듭니다.
--limit는 기본적으로 가장 최근 로그 100개로 설정되며 헤더에는 몇 개 중 몇 개를 읽었는지 표시되므로, 제한된 답변이 완전한 답변으로 오인되지 않습니다. stats는 보고만 하고 차단하지 않습니다. 아무것도 쓰지 않고, 기준선을 건드리지 않으며, 소켓을 열지 않고, 탐색이 성공하면 0으로 종료합니다.
Shadow MCP에 대해 사람들이 반복해서 말하는 발견은 조직이 승인된 것보다 몇 배 더 많은 MCP 서버가 실행 중임을 발견한다는 것입니다. 서버는 종종 누군가 IDE 플러그인에 추가한 의존성일 뿐이기 때문입니다. 같은 일이 한 대의 노트북에서도 축소되어 발생하며, mcpsnoop는 그동안 답을 기록해 왔지만 한 번도 표시하지 않았습니다.```bash mcpsnoop inventory mcpsnoop inventory --tools # also count what each server last advertised mcpsnoop inventory --format json # for something else to read
서버별로 세션이 아니라 행 하나가 서버 하나에 대응한다. 행 키는 레이블이 아니라 기록된 명령과 작업 디렉터리다. 레이블은 명령의 마지막 경로 요소에서 유래하며, `node ~/one/build/index.js`와 `node ~/two/build/index.js`는 둘 다 `index.js`를 도출하기 때문이다. HTTP 세션은 mcpsnoop이 그곳에서 아무것도 실행하지 않았으므로 대신 프록시한 엔드포인트를 키로 사용한다.
읽기는 로그 하나당 봉투 하나, 즉 프록시가 먼저 쓰는 메타 프레임이므로 대용량 캡처 디렉터리에서도 비용이 낮게 유지된다. `--tools`는 예외로 서버당 로그 하나, 즉 각 서버의 가장 최근 실행을 읽는데, 그래서 열이 아니라 플래그인 것이다. 그 경우에도 읽기는 제한적이다. 도구 인벤토리는 저장소가 진행하면서 접어 넣는 세션 상태이므로, 수백 메가바이트 캡처는 정수 하나를 만들기 위해 전체를 메모리에 담지 않고 고정 창을 통해 읽힌다.
개수가 없을 때 행은 세 가지 상황 중 어떤 것이 발생했는지를 말한다. 읽을 수 없는 로그는 아무것도 광고하지 않은 서버가 아니며, 둘을 한 문장으로 처리하면 mcpsnoop이 거짓된 내용을 진술하게 되기 때문이다.
`--redact` 규칙이 다시 쓴 명령은 기록된 대로 출력되고 표시되며, 실제 실행된 명령인 것처럼 넘겨지지 않는다. 한 서버의 두 실행, 하나는 정리되고 하나는 정리되지 않은 경우는 두 개의 행이 된다. mcpsnoop은 자리표시자가 무엇을 대체했는지 알 수 없으며, 병합하면 숨겨진 절반이 일치했다고 추측하는 셈이 된다. 두 개의 `--label` 값 아래에서 실행된 한 서버는 두 이름을 모두 담은 하나의 행이 된다. 키가 이름이 아니라 명령이기 때문이다.
행의 어떤 것도 mcpsnoop이 작성하지 않는다. 명령은 서버를 설치한 사람에게서 오고, 작업 디렉터리는 파일시스템에서 나오며, 파생 레이블은 명령에서 나온다. 제어 문자를 담은 값은 원시로 출력되지 않고 따옴표로 처리되므로, 이름에 개행이 포함된 디렉터리가 출력되는 필드를 닫아버려 다음 줄들이 실행된 적 없는 서버로 읽히는 일이 없다. 공백을 포함한 인수도 따옴표로 처리된다. `node "~/My Project/build/index.js"`는 그렇지 않으면 두 인수와 구분할 수 없기 때문이다.
걷기(walk)가 접어 넣지 못한 것은 버려지지 않고 헤더에 이름이 명시된다. 빈 로그는 손상된 로그와 별도로 집계된다. 0바이트 로그는 exec가 실패한 실행이나 아무도 호출하지 않은 HTTP 프록시의 일반적인 잔재이기 때문이다.
출력은 최신순이 아니라 이름순으로 정렬되어 한 디렉터리에 대한 두 번의 실행이 동일한 바이트를 생성하며, 이것이 나중에 diff할 기준선으로 사용할 수 있게 만드는 이유다.
두 가지 공백은 실수 때문이 아니라 설계상 존재한다. `--trace-file`이 있는 실행은 세션 디렉터리 밖에 기록했으므로 나타나지 않으며, `prune`은 로그를 삭제하므로 첫 발견 시점은 디스크에 남아 있는 것만큼만 오래될 수 있다. mcpsnoop은 이 머신에서 그것을 통해 실행된 것을 보고한다. 네트워크를 스캔하지 않고, 가리키지 않은 클라이언트 구성을 읽지 않으며, 어떤 것도 판단하지 않는다.
### 고장난 서버와 거절하는 도구 구분하기
`result.isError`로 응답하는 도구는 정상 작동 중이다. 살펴보고 아무것도 찾지 못했거나 입력을 거부한 것이다. JSON-RPC 오류로 응답하는 서버는 고장난 것이다. 둘 다 도구 요약에서 하나의 숫자였는데, 이는 도메인 실패를 보고하는 잘 작동하는 도구가 고장난 서버와 똑같이 보이고 그 위로 정렬된다는 뜻이었다.
`ERR` 열이 둘을 구분한다. 빨간색은 서버 측, 즉 JSON-RPC 오류 또는 이유를 밝히지 않고 실패로 끝난 작업이다. 경고 색상은 도구 자체의 `isError`다. 둘 다 있는 도구는 개수가 결합되어 표시되며 빨간색이 먼저 오고, 설명할 경고 숫자가 있을 때마다 표 아래 줄이 두 합계를 명명한다. 내보내기는 `errors` 총계 옆에 `protocol_errors`와 `tool_errors`로 동일한 구분을 담으며, 둘은 항상 그 총계에 합산된다.
`check --fail-on error`는 변경되지 않았으며 어느 쪽에서든 여전히 발동한다. 둘 중 하나를 무시하는 게이트는 서버가 다른 쪽을 반환하여 끌 수 있는 게이트가 되기 때문이다.```bash
mcpsnoop export -T json | jq '.summary.tools[] | {name, errors, protocol_errors, tool_errors}'
도구 정의는 모든 대화에서 모델의 컨텍스트에 들어가고, 도구 결과는 모든 호출에서 들어갑니다. 도구 요약(s)은 실제로 캡처한 세션에서 두 가지를 모두 측정합니다.
definitions 줄은 고정 비용입니다. 이 서버의 tools/list가 단 한 번의 호출도 이루어지기 전에 차지하는 용량입니다. DEF 열은 이를 도구별로 세분화하고, RESULT는 각 도구의 응답이 지금까지 소모한 비용입니다. 테이블은 오류와 지연 시간별로 정렬되어 있으므로 DEF를 훑어 비싼 정의를 찾아내세요. 내보내기는 가장 무거운 것부터 나열합니다. 테이블 아래의 줄은 총계가 숨기는 단일 최대 결과를 명시합니다.
정의 수치는 의미 없는 공백을 제거한 JSON이므로, tools/list를 예쁘게 출력하는 서버가 그렇지 않은 서버보다 더 비싸게 계산되지 않으며, 동일한 서버는 캡처 간에 동일하게 측정됩니다. RESULT는 도착한 그대로의 바이트입니다. 결과는 정규화할 가치가 있는 계약이 아닌 일회성 페이로드입니다.```bash
mcpsnoop export -T json | jq '.summary.definitions'
내보내기는 도구별로 설명과 스키마 바이트로 구분된 동일한 수치를 담고 있어, 비대한 설명과 비대한 스키마가 분리된 상태를 유지하며 각각 캡처 간 추적할 수 있습니다. `mcpsnoop diff`는 두 세션 사이에 설명이나 스키마가 변경되었는지 알려줍니다. 그 변경의 크기가 존재하는 곳이 바로 내보내기입니다.
**이것들은 바이트이지 토큰이 아닙니다.** 토큰 수는 모델에 따라 달라지므로, 이를 측정하려면 토크나이저를 포함하고 어떤 것을 선택할지 정해야 합니다. 바이트는 정확하며 자체 비율을 적용할 수 있습니다. 완료되지 않은 `tools/list`는 본 것을 하한선으로 보고하고 그렇게 명시하며, 부분 합계를 총계로 속이지 않습니다.
### 서버 상태를 훼손하는 클라이언트 감지
다중 왕복 패턴에서 서버는 클라이언트에게 불투명한 `requestState`를 건네주고, 클라이언트는 재시도 시 이를 그대로 다시 에코해야 합니다. 서버는 이를 공격자 통제 입력으로 취급하라고 지시받는데, 이를 변조하는 클라이언트는 서버 동작을 변경하거나 인가 검사를 우회하려 시도할 수 있기 때문입니다.
파이프에 위치한 mcpsnoop는 값이 나가고 돌아오는 것을 보므로, 계약이 깨졌을 때를 말할 수 있습니다. 깨질 수 있는 세 가지 방식이 있으며, 각각 재시도 시 프로토콜 경고로 보고됩니다.
| 보고됨 | 의미 |
|---|---|
| `MRTR retry changed requestState` | 클라이언트가 서버가 발급한 것과 다른 것을 다시 보냄 |
| `MRTR retry is missing requestState` | 서버가 발급했지만 재시도가 이를 생략함 |
| `MRTR retry invented requestState` | 재시도가 서버가 발급한 적 없는 것을 담고 있음 |
이것들은 우리의 관찰이 아니라 클라이언트의 프로토콜 위반이므로, 일반 경고 신호를 타고 **기본 `check` 실행은 하나라도 있으면 실패합니다.** 이는 의도적입니다. 서버 상태를 훼손하는 클라이언트는 빌드를 중단할 가치가 있습니다.
값 자체는 표시되거나 기록되지 않으며, 어떤 것도 이를 디코딩하거나 파싱하지 않습니다. 이는 주체와 토큰을 담은 암호화된 블롭일 수 있으며, 불투명한 바이트를 비교하는 것이 전체 검사입니다.
한 가지 경우는 도달할 수 없습니다. 서버가 `requestState`로 응답하고 `inputRequests`가 없을 때, 변조된 재시도는 어떤 것과도 일치하지 않고 어떤 키에도 응답하지 않으므로 원래 요청에 연결할 것이 남지 않아 위반이 아닌 무관한 호출로 읽힙니다.
버려진 교환은 다음 교환을 방해하지 않으며, 영원히 보관되지도 않습니다. 64개의 열린 교환은 어떤 클라이언트가 동시에 보유하는 것보다 훨씬 많으므로, 그보다 많이 보유한 세션은 아무도 완료하지 않을 것들을 보유한 것이며, 가장 오래된 것은 서버에 그 상태에 짧은 만료를 부여하고 이후에는 거부하라고 스펙이 지시하기 때문에 폐기됩니다. 폐기는 조용히 이루어지지 않고 집계됩니다. 스트림 푸터는 `N unlinked`를 표시하고 내보내기는 `session.retired_exchanges`를 담는데, 폐기된 작업에 도착하는 재시도는 자체 호출로 읽히므로, 개수를 비교하는 독자는 그 사실을 알려받을 자격이 있기 때문입니다.
하나를 폐기하면 라이브 저장소도 이를 해제할 수 있습니다. 대기 중인 작업은 의도적으로 보류 상태를 유지하므로 그 기간이 전체 교환에 걸치며, 저장소는 응답이 여전히 올 수 있으므로 대기 중인 호출을 잊기를 거부합니다. 상한선이 작업을 폐기한 후에는 어떤 것도 응답할 수 없으므로, 이를 보유하면 어떤 독자도 도달할 수 없는 호출을 살아 있게 유지하는 것입니다. 세션이 보고하는 것은 변하지 않습니다. 여전히 대기 중으로 집계되고 `N unlinked`에도 집계되는데, 레코드가 차지하는 메모리 양과 레코드가 말하는 것은 서로 다른 질문이기 때문입니다.
버려진 교환은 다음 교환을 방해하지 않습니다. MRTR은 서버에게 클라이언트가 재시도할 것이라고 가정해서는 안 된다고 지시하므로, 사용자가 유도(elicitation)를 거절하면 이후 어떤 프레임도 해결하지 못할 작업이 남습니다. mcpsnoop는 먼저 `requestState` 존재가 재시도의 것과 일치하는 작업들 사이에서 찾는데, 스펙이 이를 양방향 규칙으로 만들므로, 준수하는 재시도는 같은 도구에 버려진 교환이 옆에 앉아 있어도 자신이 이어가는 하나의 작업을 여전히 찾습니다. 위의 세 가지 위반을 보고하는 검사는 아무것도 일치하지 않을 때만 실행되므로, 진정으로 비준수하는 재시도도 여전히 지목됩니다.
## 다른 머신에서 감시하기
캡처는 트래픽이 발생하는 머신에 국한하고 네트워크 홉에는 SSH를 사용하세요. 그러면 mcpsnoop는 자체 원격 전송이 필요하지 않습니다.
### 실시간 보기
워크스테이션에서 TUI를 실행하고 원격 머신의 mcpsnoop 소켓을 다시 전달하세요. 실시간 터널은 SSH Unix 소켓 전달을 사용하므로 양쪽 끝이 Linux 또는 macOS를 실행해야 합니다. Windows에서는 아래의 사후 로그 복사를 사용하세요.```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
mcpsnoop remote --remote-home /Users/remote-user remote-user@remote-host
mcpsnoop remote --remote-mcpsnoop-home /srv/mcpsnoop remote-user@remote-host
mcpsnoop remote --remote-xdg-state-home /var/lib/state remote-user@remote-host
### 사후 분석(Post-mortem)
SSH를 통해 원격 세션을 TUI로 직접 스트리밍하세요. 로컬 복사본이 필요 없습니다.```bash
ssh remote-user@remote-host 'cat ~/.local/state/mcpsnoop/sessions/session.jsonl' | mcpsnoop open -
로컬 복사본을 유지하려면 대신 로그를 세션 디렉터리로 scp한 후 TUI를 평소처럼 실행하세요.```bash
mkdir -p /.local/state/mcpsnoop/sessions
scp remote-user@remote-host:'/.local/state/mcpsnoop/sessions/*.jsonl'
~/.local/state/mcpsnoop/sessions/
mcpsnoop
## 보안
mcpsnoop은 래핑하는 서버 명령을 실행하므로, 신뢰하는 서버만 래핑하고 신뢰할 수 없는 서버는 컨테이너에서 실행하세요. 클라이언트 구성에 넣지 않은 것은 절대 실행하지 않습니다.
원격 워크플로의 경우 SSH 터널링 또는 SSH 파일 전송을 사용하여 전송 인증, 암호화, 호스트 검증, 키 순환 및 감사 정책이 기존 SSH 설정에 유지되도록 하세요.
### 캡처 내용 편집(Redacting)
캡처된 프레임에는 프롬프트, 도구 인수, 자격 증명 및 도구 결과가 포함될 수 있습니다. 페이로드에 비밀 정보가 포함될 수 있다면 편집(redaction)을 선택하여 관찰된 트레이스 복사본을 삭제하는 동시에 프록시된 바이트는 변경 없이 그대로 통과시키세요.
키 기반 편집은 일치하는 JSON 객체 키 아래의 전체 값을 대체하며, 동일한 키 집합은 래핑된 서버의 명령줄 인수에도 최선의 노력으로 적용되므로 `--api-key=sk-x` 및 `--token sk-x`는 `--redact-secrets` 아래에서 삭제됩니다. 인식 가능한 플래그 이름 없이 비밀 정보를 담은 인수는 감지할 수 없습니다.
HTTP 엔드포인트는 그러한 범위에 포함되지 않습니다. 이는 사용자가 보내기로 선택한 페이로드가 아니기 때문입니다. `--target`은 프록시를 실행하기 위해 반드시 전달해야 하는 플래그이므로, 해당 URL은 편집 설정과 관계없이 세션 로그에 도달합니다. mcpsnoop은 이를 사용자 정보, 모든 쿼리 값 및 프래그먼트가 이미 제거된 상태로 항상 기록하며, 패턴이 아닌 구조적으로 그렇게 처리합니다. 쿼리 키는 유지되는데, 이는 한 호스트의 두 엔드포인트를 구분하는 요소이기 때문이며, 프래그먼트는 애초에 서버에 도달하지 않았으므로 삭제됩니다. 기록되는 내용은 서버를 식별할 뿐 연결을 시도할 주소가 아닙니다.
경로 기반 편집은 JSONPath 표현식으로 선택된 값만 대체하며, 공통 키 이름이 한 위치에서는 민감하지만 다른 위치에서는 안전할 때 유용합니다. `--redact-path`를 반복하여 둘 이상의 위치를 삭제하세요.
값 기반 편집은 관찰된 문자열 값, stderr 텍스트 및 비-JSON 텍스트 프레임에 정규식을 적용합니다.
세 가지 모두 최선의 노력 방식입니다. 정규식은 비밀 정보를 놓치거나, 무해한 텍스트를 과도하게 일치시키거나, 변환되거나 인코딩된 값을 감지하지 못할 수 있습니다.
편집은 결코 비난으로 변하지 않습니다. 관찰된 한 항목을 다른 항목과 비교하는 모든 검사(라우팅 헤더와 본문, `Mcp-Param` 값과 그것이 반영하는 인수, 도구 스키마와 개정판이 요구하는 사항)는 mcpsnoop이 바이트를 다시 쓴 쪽이었을 때 이를 인지하고, 사용자 자신의 개인정보 설정에 대해 서버를 보고하지 않고 침묵을 유지합니다. 도구 정의 드리프트는 의도적으로 예외인데, 편집을 켜면 기록되는 내용이 변경되고 따라서 기준선이 보유하는 내용도 변경되기 때문입니다. [도구 정의 드리프트 감지](#detect-tool-definition-drift)를 참조하세요.```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+'
이슈와 풀 리퀘스트는 언제나 환영합니다. 자세한 내용은 CONTRIBUTING.md를 참조하세요.