mcp-snapshot-filter
# mcp-snapshot-filter
MCP proxy that sits between [Claude Code](https://claude.com/claude-code) and [`chrome-devtools-mcp`](https://github.com/nichochar/chrome-devtools-mcp), intercepting content-heavy responses and applying smart filtering to reduce token usage by **40-60%**.
```
Claude Code <--stdio--> mcp-snapshot-filter <--stdio--> chrome-devtools-mcp
```
## Why?
Browser MCP tools (especially `take_snapshot`) return massive accessibility trees that fill up the context window fast. A single snapshot of a data-heavy page can be 30-40KB of text. This proxy transparently filters that down without breaking any tool interactions.
**Real-world results:**
| Page | Original | Filtered | Reduction |
|---|---|---|---|
| GitHub repo page | 29.5KB | 22.8KB | 23% |
| Portal with 36-row data table | 38.5KB | 17.3KB | 55% |
## Setup
```bash
# Clone and build
git clone https://github.com/Akarda/mcp-snapshot-filter.git
cd mcp-snapshot-filter
npm install
npm run build
```
Add to `~/.claude.json` (replace any existing `chrome-devtools` entry):
```json
{
"mcpServers": {
"chrome-devtools": {
"type": "stdio",
"command": "node",
"args": [
"/path/to/mcp-snapshot-filter/dist/index.js",
"--filter-level=moderate",
"--",
"npx", "-y", "chrome-devtools-mcp@latest"
]
}
}
}
```
Restart Claude Code to pick up the new config.
## Filter Levels
| Setting | Light | Moderate (default) | Aggressive |
|---|---|---|---|
| maxNodes | 1000 | 500 | 300 |
| maxDepth | 20 | 15 | 10 |
| maxSimilarSiblings | 5 | 3 | 2 |
| Strip decorative nodes | yes | yes | yes |
| Collapse navigation | no | yes | yes |
| Focus main content only | no | no | yes |
| Est. reduction | 20-30% | 40-60% | 60-80% |
Use `--filter-level=off` to disable all filtering and pass through raw responses.
## What Gets Filtered
### Snapshot filtering (biggest impact)
- **Decorative nodes removed** — `role=none/presentation/separator`, empty text nodes, unnamed generic wrappers are stripped (children promoted up)
- **Similar siblings collapsed** — after N consecutive siblings with the same role (e.g., 50 `StaticText` nodes in a table), keeps first N and shows `... [47 more StaticText elements collapsed]`
- **Navigation collapsed** — `navigation/banner/contentinfo/menu` subtrees reduced to a single summary: `[navigation: 21 items, 9 links]`
- **Main content focus** (aggressive only) — when `role=main` exists, sibling subtrees are summarized
- **Depth/node limits** — prunes beyond maxDepth, caps total node count
### Network request filtering
Strips `image`, `font`, `stylesheet` (and `media` in aggressive) entries from `list_network_requests` output. All `xhr`, `fetch`, `document` requests remain visible. `get_network_request` is **never filtered** — individual request details always pass through in full.
### Console message filtering
Strips `debug`, `verbose` (and `dir`, `trace` at higher levels) from `list_console_messages`. Error and warning messages are always preserved.
### Evaluate script & memory snapshot filtering
Large responses from `evaluate_script` and `take_memory_snapshot` are truncated:
- **JSON arrays** — kept to first N items (100/30/10 depending on level)
- **HTML dumps** — truncated at max text length
- **Plain text** — truncated at max text length (100KB/50KB/20KB depending on level)
## What Is NOT Filtered
- **Error responses** — always passed through raw
- **Image content blocks** — screenshots etc. are never touched
- **Unknown/new tools** — only explicitly listed tools get filtered, everything else passes through
- **Individual request/message details** — `get_network_request`, `get_console_message` are unfiltered
- **Non-interactive tools** — `emulate`, `performance_*`, `screencast_*`, etc. pass through as-is
## UID Integrity
All UIDs of visible nodes are preserved in filtered output. After taking a filtered snapshot, you can `click`, `fill`, or `hover` any UID shown in the output — the proxy forwards these calls to the upstream server unchanged.
## Session Stats
Each filtered response includes a stats line:
```
[cdp-filter-proxy: 20.7KB saved (55%) | session: 22.4KB saved across 2 calls (52%)]
```
## How It Works
The proxy uses the MCP SDK's low-level `Server` class (not `McpServer`) to avoid JSON Schema to Zod conversion issues when proxying tool definitions. On startup it:
1. Spawns the upstream `chrome-devtools-mcp` as a subprocess via `StdioClientTransport`
2. Discovers all upstream tools via `listTools()`
3. Re-exposes them via `setRequestHandler(ListToolsRequestSchema)` and `setRequestHandler(CallToolRequestSchema)`
4. For calls to content-heavy tools, parses the response text into markdown sections (`## Latest page snapshot`, etc.), applies the appropriate filter to each section, and returns the filtered result
## Compatibility with `--slim` mode
chrome-devtools-mcp supports a `--slim` flag that reduces the server to just 3 tools (`navigate`, `evaluate`, `screenshot`) for maximum token savings (~368 tokens). If the proxy detects `--slim` in the upstream args, it will warn that filtering has no effect — slim mode responses are already minimal. The proxy is designed for full mode where it preserves all tools while reducing response sizes by 40-60%.
## Development
```bash
npm run build # compile TypeScript to dist/
```
After rebuilding, restart Claude Code to pick up changes (MCP servers are long-lived processes).
TDQS
Scored across 29 tools
Most tools target a clear, distinct browser action or data source, such as navigation, input, console messages, network requests, or performance tracing. The main ambiguity is between fill, fill_form, and type_text, though the descriptions reduce the risk of misselection.
Most names follow a readable snake_case verb_noun pattern like list_pages, navigate_page, and take_snapshot. However, bare verbs such as click, fill, hover, and drag, plus deviations like lighthouse_audit, new_page, and wait_for, break the otherwise consistent style.
At 29 tools, the surface is too large for a tightly scoped server and crosses the 25+ threshold. Several specialized debugging capabilities like heap snapshots, Lighthouse audits, and performance tracing could be separated into their own servers or grouped more compactly.
The tool set covers the full browser workflow well: page lifecycle, navigation, interaction, forms, dialogs, console and network inspection, screenshots, accessibility audits, performance tracing, and memory analysis. Minor gaps like direct cookie/storage manipulation or an explicit scroll helper can be worked around with evaluate_script or emulate.