MCP server
@motionvector/webcodecs-census-mcp
Let an agent find a leaked VideoFrame — with the allocation stack — without a
browser UI in the loop.
Existing media tooling is built for human eyes: panels, flamegraphs, screenshots. An agent cannot read those efficiently. This exposes the same information as small, structured text.
Register it
{
"mcpServers": {
"webcodecs-census": {
"command": "npx",
"args": ["-y", "@motionvector/webcodecs-census-mcp"]
}
}
}
It speaks stdio and holds one session at a time. SIGINT and
SIGTERM detach and close any browser it launched.
Tools
| Tool | What it answers |
|---|---|
webcodecs_attach | Instrument a page and every one of its Web Workers. Call first. |
webcodecs_census | What is open right now, as a digest. |
webcodecs_leak_sites | Which line is leaking — grouped by allocation site, worst first. |
webcodecs_timeline | Live counts, throughput, queue depth and media readiness over time. |
webcodecs_evaluate | Drive the app so the census has activity to observe. |
webcodecs_detach | Stop, and close any browser this launched. |
webcodecs_attach
| Parameter | Type | Default | What it does |
|---|---|---|---|
url | string | — | Page to open and instrument. The server navigates, then settles briefly before returning. |
browserURL | string | — | DevTools endpoint of a Chrome already started with --remote-debugging-port. |
executablePath | string | — | Chrome binary to launch instead, with a throwaway profile. |
headless | boolean | true | Only meaningful with executablePath. |
sampleIntervalMs | number | 250 | Timeline sampling interval. The server keeps 400 samples per context. |
One of browserURL or executablePath is required. Calling it while
already attached returns a note rather than replacing the session — detach first.
webcodecs_census
Returns summarize() for every instrumented context, plus anything that could
not be instrumented, under a Not instrumented: heading. Small by design — use
webcodecs_leak_sites for stacks.
Takes waitMs (default 0) to settle before sampling.
webcodecs_leak_sites
| Parameter | Default | What it does |
|---|---|---|
type | every tracked type | Restrict to one type, e.g. VideoFrame. |
limit | 10 | Maximum sites returned. |
minAgeMs | 0 | Ignore sites whose oldest object is younger than this. |
This tool covers every tracked type unless you name one. It deliberately does not inherit the assertion API's default of frame-like types only: an agent asking "which line is leaking" about a codec leak would otherwise get nothing back.
webcodecs_timeline
Takes context (restrict to one context name) and lastN (default
40).
webcodecs_timeline { context: "worker", lastN: 20 }
### worker (20 samples)
t(ms) live dec/out queued media(stalled)
12000 VF=59 0/0 0 4(0)
58000 VF=58 0/0 0 4(1)
162000 VF=58 0/0 0 4(1)
VF is VideoFrame; other types abbreviate to their initial.
dec/out is decode calls over output callbacks in that interval, and
queued is summed codec queue depth.
Decoder idle, queue empty, count frozen: wedged, not buffering. A single snapshot cannot tell those apart, and the difference is between chasing a resource limit that does not exist and finding the actual bug.
webcodecs_evaluate
Runs an expression in the page — click something, start playback — so the census has
activity to observe. Takes expression, which is required. Returns the value as
JSON.
webcodecs_detach
Stops instrumenting and closes any browser the server launched. No parameters.
A session
webcodecs_attach { executablePath: "/path/to/chrome", url: "http://localhost:5173/" }
→ Attached.
Instrumented 3 context(s):
page http://localhost:5173/
worker http://localhost:5173/decode-worker.js
worker blob:http://localhost:5173/…
Workers are instrumented before their first line, so allocations at worker
startup are counted.
webcodecs_census
→ worker (20s): VideoDecoder=1 VideoFrame=58
1 VideoFrame garbage collected without close() — definitively leaked.
webcodecs_leak_sites { type: "VideoFrame" }
→ 58x VideoFrame — decoded, oldest 124609ms, in worker
at PackagerWorker.setupDecoder (worker.js:1756:21)
(frame emitted by this VideoDecoder)
Running alongside chrome-devtools-mcp
Several CDP clients can attach to one page at the same time, so this runs happily beside
chrome-devtools-mcp.
They cover different ground: heap snapshots measure the JS heap, and a WebCodecs resource
lives outside it — a page-target snapshot reports tens of bytes for frames holding
megabytes of GPU memory, does not cover worker isolates, and cannot see a frame GC already
collected without close().
webcodecs_evaluate runs arbitrary JavaScript in the page under test — that
is how an agent drives the app it is measuring. Anything driving this server can run code
in any page it attaches to. Point it at applications you control, and never expose a
debugging port beyond localhost.