-
-
When the agent needs specific rows, you see why, SQL, and the exact preview. You choose what to release - or say no. Nothing auto-approves
-
Airlock: AI analytics in your browser. Data stays in the tab. The counter shows how many values you actually released — starting at zero.
-
One click runs a full gender pay-gap review: charts in the shared notebook, every tool call in the audit log. Aggregates only — no raw rows.
-
The agent tries to pull individual rows. Blocked by default. run_query only returns grouped results — not a setting you forgot to turn on.
-
Turn off raw row requests: the tool is crossed out and gone from the agent's list. Permission is capability control - not a polite error msg
-
Full story: ~7% gap within grade, not ~20% headline. Then a controlled release - only the columns you approved, counted on screen.
Inspiration
Analysts sit on payroll files, patient extracts, and transaction logs they cannot paste into ChatGPT. The barrier is policy, not preference. Until WebMCP, the browser had no way to offer AI help without an upload.
What it does
Airlock is a local-first analytics workspace. Your file loads into DuckDB in the tab and never leaves the device. An AI agent works through 14 WebMCP tools — profiling, guarded SQL, charts, findings — and returns aggregates, not rows.
When individual values are truly needed, the agent must ask in writing. You see the SQL, the preview, and the cell count before you approve. A header counter tracks every value that crossed the boundary.
One click runs a full gender pay-gap demo:
- Headline gap ~20%
- Within-grade gap ~7%
- Cells released: 0
Try it: live demo · source on GitHub
Why WebMCP is the only way to build this
A remote MCP server cannot read a file that never left the browser. Uploading defeats the purpose. Screen-reading agents see every rendered row — the boundary is gone on contact.
WebMCP runs the code that touches the data and the code that filters what the model receives in the same page. execute is the choke point:
// Tier 3 gone → tool unregistered, not just refused
controller.abort();
// request_raw_rows disappears from getTools()
When raw requests are disabled, we abort the tier's AbortController and request_raw_rows vanishes from the agent's tool list — it cannot call a tool it cannot see.
How we built it
- Next.js 16 static export — no backend, no API routes
- DuckDB-Wasm for in-tab SQL
- 14 tools in one Zod-backed registry via
document.modelContext.registerTool() - Tiers map to AbortController lifetimes: page open → dataset loaded → raw requests permitted
Every handler returns an envelope — never throws:
{ "ok": true, "summary": "...", "data": { ... } }
{ "ok": false, "error": "too_many_rows", "hint": "Use aggregates or request_raw_rows" }
Privacy pipeline: SQL guard → aggregate-only run_query → column redaction → k-anonymity → human gate on raw release.
Privacy model, honestly
We claim: no server, no upload, provable cell count, explicit release approval.
We do not claim: protection against a patient adversary reconstructing rows from many narrow aggregates. That limit is stated openly in the app.
Challenges we ran into
- DuckDB-Wasm inside a static export (no
Workerat build time) - React Strict Mode double-registering WebMCP tools
- Observable Plot tooltips on a dark theme
- Making capability withdrawal visible in the UI — red dot, strikethrough — not just in code
Accomplishments that we're proud of
- A complete pay-gap review with the counter at 0
- Permission as tool lifetime, not tool logic
- One registry serving WebMCP agents, the in-page demo, and manual inspection
- Judges can verify in DevTools → Network: no request carries your data
What we learned
Tool descriptions are part of the product — agents read failures and hints. The airlock meter turns an abstract privacy promise into something a judge can glance at. Presentation beats feature lists in a hackathon.
What's next for Airlock
OPFS session persistence, org-side audit export, AST-tightened SQL guard, query-budget accounting against aggregate reconstruction.
Built With
- data-analytics
- duckdb
- model-context-protocol
- next.js
- observable-plot
- openai
- privacy
- react
- sql
- tailwind-css
- typescript
- vercel
- web-workers
- webassembly
- webmcp
- zod
- zustand
Log in or sign up for Devpost to join the conversation.