v0.7.0 — MCP proxy for AI agents

PlayGuardThe MCP proxy for Playwright MCP and Figma MCP

One MCP server replaces both. It revives dead browser sessions and strips 70–90% of snapshot tokens before any of it reaches your agent.

70–90%fewer snapshot tokens
1–3 msproxy overhead
0install steps
.mcp.json
{
"mcpServers": {
"playguard": {
"command": "npx",
"args": ["-y", "playguard"],
"env": {
"PLAYGUARD_SCREENSHOTS": "redirect",
"FIGMA_MCP_CMD": "npx @figma/mcp",
"FIGMA_API_KEY": "your-figma-api-key",
"FIGMA_CACHE_TTL": "60000"
}
}
}
}

Drop it in · MIT licensed · Node 18+ · Tests in CI

Works with Claude Code · Claude Desktop · Cursor · Codex · any MCP client

AI AGENTS
CC
Claude Code
CU
Cursor
CX
Codex
CD
Claude Desktop
P
PlayGuard
MCP · stdio
MCP SERVERS
PW
Playwright MCP
Browser automation
FG
Figma MCP
Design data
Optional
Inside the proxy

What happens to every tool call

01 · ROUTE

Unified entry

One MCP server for everything. Calls are routed by prefix — the agent config stays one entry.

browser_* → Playwrightfigma_* → Figma
02 · RECOVER

Crashes intercepted

Dead-session errors never reach the agent. PlayGuard restarts the browser, restores the last URL, retries the call.

Target closedECONNREFUSEDcrashed
03 · OPTIMIZE

Responses shrunk

Snapshots are compacted to interactive elements, page changes become diffs, Figma JSON is deduped and stripped.

compactdeltafigma pipeline
04 · CACHE + LOG

Nothing fetched twice

Snapshots prefetch in the background, evals are cached by TTL, and every call lands in NDJSON logs for npm run analyze.

prefetcheval cacheanalytics
Token optimization

Same page, minus the noise

interactive [ref=] — always kept
landmarks (nav, main, form) — kept
static text & decor — no refs, nothing to click — removed
raw accessibility tree124.3 KB · 1 840 lines
- banner:
- text: "Free shipping over $50"
- img "hero-decoration.png"
- navigation:
- link "Products" [ref=12]
- link "Pricing" [ref=13]
- text: "New arrivals every week"
- main:
- heading "Spring Collection"
- paragraph: "Discover our latest…"
- img "product-photo-1.jpg"
- img "product-photo-2.jpg"
- button "Add to cart" [ref=31]
- form:
- textbox "Email" [ref=44]
- button "Subscribe" [ref=45]
… 1 824 more lines
-83%
COMPACT
what the agent sees14.1 KB · 312 lines
[PlayGuard compact: 312/1840 lines,
~83% removed, 124.3KB→14.1KB]
- navigation:
- link "Products" [ref=12]
- link "Pricing" [ref=13]
- main:
- button "Add to cart" [ref=31]
- form:
- textbox "Email" [ref=44]
- button "Subscribe" [ref=45]

Removed lines are static text and decorative images — they carry no [ref=], so the agent can't interact with them anyway. Every interactive element survives, always. Need the full tree? PLAYGUARD_COMPACT=false returns the raw snapshot.

page changed slightly? you get a diff, not a new snapshot~91% saved
[PlayGuard delta: +1 added, 1 removed, ~91% saved]
ADDED:
- button "Submit" [ref=47]
REMOVED:
- button "Loading..." [ref=44]
70–90%
snapshot cost cut
-68%
figma tokens
~1–3 ms
proxy overhead
0
crashes seen by agent
Figma optimizer

Design files, minus the bloat

layout & structure — kept
collapsed to a reference
metadata, invisible layers, geometry blobs — removed
raw Figma REST JSON284.0 KB
{
"name": "Checkout Flow",
"createdAt": "2026-05-01T09:12:44Z",
"thumbnailUrl": "https://s3…&sig=…",
"pluginData": { … 4.2 KB … },
"children": [
{ "visible": false, … 12 nodes … },
{ "type": "INSTANCE",
"name": "Button/Primary",
"fillGeometry": [ … 9.8 KB … ],
"x": 240, "y": 812,
… full definition × 100 …
},
]
}
-68%
7 MODULES
what the agent sees91.0 KB
[PlayGuard figma: -68% (284.0KB→91.0KB)]
{
"name": "Checkout Flow",
"children": [
{ "type": "INSTANCE",
"name": "Button/Primary",
"_ref": "123:4",
"_svgRef": "123:7",
"overrides": []
},
… 99 more references …
]
}
1 · metadata cleaner2 · invisible layers3 · component dedup4 · SVG refs5 · metadata trim6 · layout compressor7 · budget trim

100 instances of the same button? The agent gets one full definition + 99 references with overrides. Geometry blobs become _svgRef — the shape id without thousands of path coordinates. Layout structure is untouched. Still over budget? The tree is trimmed structurally, not sliced as text — oversized branches collapse to a stub the agent can re-fetch by id, so nothing vanishes silently.

Design diff

Figma vs. the live DOM, in numbers

playguard_compare_design reads a Figma node and the DOM element that implements it, then reports the exact deltas. No screenshots, no eyeballing, no "looks about right".

playguard_compare_design · 42:1337 → [data-testid="login-btn"]2 mismatches
fontSize: 16px → 14px MISMATCH +2.0px
padding: 12px 24px → 12px 16px MISMATCH Δx:+8px
color: rgb(255,255,255)→ rgb(255,255,255) MATCH
backgroundColor: rgb(25,118,210) → rgb(25,118,210) MATCH
borderRadius: 8px → 8px 8px 8px 8px MATCH
boxShadow: none → none MATCH
margin: — → 0px UNKNOWN (no Figma concept)

It picks the properties

A TEXT layer is compared on typography and colour; a container on background, padding, radius and shadow. Pass properties[] only when you disagree.

Box values compare side by side

8px 8px 0 0 never slips through as a match for 8px. Tolerances for size and per-channel colour are yours to set.

Unset is not unknown

A shadow Figma never defined is still checked against the browser, so a stray value gets caught. margin is reported as unknown — Figma has no such concept.

Auto-map never guesses

Layers match DOM by data-figma-id, data-testid, id, class or exact text. A selector hitting zero or several elements is reported unmapped, not silently attached to the wrong node.

single elementbatch pairs[]autoMap: true
What it does

One proxy, six upgrades

Auto Recovery

Dead browser sessions are revived automatically. The agent never sees a crash.

Token Optimization

Compact snapshots and delta diffs cut response size 70–90%.

Smart Caching

Snapshot prefetch and eval caching skip redundant round-trips.

Screenshot Control

Redirect, warn, block, or allow screenshot calls per policy.

Figma Optimizer

Strips metadata, dedupes components, and drops SVG geometry noise.

Design Diff

Compares a Figma node against the live DOM and reports the exact mismatches.

Side by side

What changes the day you add it

Scenario
Without PlayGuard
With PlayGuard
Browser crashes mid-task
Error surfaces; session is broken
Restarted, URL restored, call retried — agent never sees it
Two calls land during a crash
Two failures, or two browsers
One restart; both calls continue
browser_snapshot (full tree)
100–500 KB of accessibility tree
10–80 KB — only refs and landmarks
Same page snapshotted twice
Re-fetched every single time
Cache hit, or a bare UNCHANGED
Figma file with 50+ components
200–500 KB of JSON
60–150 KB after 8 optimizer modules
Two MCPs in your agent config
Two servers to wire up and debug
One PlayGuard entry
Proxy cost
None
~1–3 ms per Playwright call

Every number above comes from npm run bench and the NDJSON call log PlayGuard writes on every run — no LLM in the loop, just measured bytes and milliseconds. Run npm run analyze on your own logs to see your numbers.

Setup

Drop it into your agent's config

.mcp.json
{
"mcpServers": {
"playguard": {
"command": "npx",
"args": ["-y", "playguard"],
"env": {
"PLAYGUARD_SCREENSHOTS": "redirect",
"FIGMA_MCP_CMD": "npx @figma/mcp",
"FIGMA_API_KEY": "your-figma-api-key",
"FIGMA_CACHE_TTL": "60000"
}
}
}
}
Analytics

npm run analyze

playguard — zsh
Requirements

Node 18+. Nothing to build.

npx pulls the latest version from npm the moment your agent starts it — no clone, no build step, no manual updates.

Before you install

The questions you're about to ask

Do I have to replace my Playwright MCP setup?

You remove it. PlayGuard bundles Playwright MCP as a dependency and spawns it itself — adding both to your config runs two browsers. One entry replaces both.

What if I don't use Figma?

Leave FIGMA_MCP_CMD unset and PlayGuard runs browser-only, with no behaviour changes. The Figma optimizer never loads.

Can it break my agent by hiding something?

Compaction keeps every line carrying a [ref=] plus structural landmarks — exactly what an agent needs to navigate. Set PLAYGUARD_COMPACT=false and you get the raw tree back. Budget trimming collapses oversized branches into re-fetchable {id, name, type, _stub} markers instead of dropping them, so nothing disappears silently.

What does the proxy cost me?

About 1–3 ms per Playwright call, measured by npm run bench. Against a snapshot that shrinks from 300 KB to 30 KB, that is not the number worth worrying about.

Where do my API keys go?

Nowhere but the child process. FIGMA_API_KEY is forwarded to your Figma MCP exactly as you set it. PlayGuard runs locally over stdio, talks to no server of its own, and phones nothing home.

How do I know the savings are real?

Every call is logged to logs/YYYY-MM-DD.ndjson with raw and kept byte counts. npm run analyze turns your own logs into a report — cache hits, bytes saved, latency percentiles per tool.

Is it maintained, and can I read it?

MIT licensed, source on GitHub, CI runs the full suite on every push and PR — the compactor, all 8 Figma optimizer modules, crash detection, caching, and design-diff extraction each have tests.

What about screenshots?

Your call: allow, warn (default), block, or redirect — the last swaps a screenshot for a snapshot, with {visual:true} as the escape hatch when you genuinely need pixels.

Want the long version? How PlayGuard works walks through every mechanism in full — compaction, recovery, the eight Figma modules, and every environment variable.

Stop paying for noise.

One proxy in your config. 70–90% fewer snapshot tokens. Zero crashes seen by your agent.
MIT licensed · runs locally over stdio · nothing phoned home · read every line before you trust it.