Inspiration

Peer reviewers are bound by confidentiality. ICMJE, Elsevier, COPE and NIH all restrict putting a manuscript under review into an AI service, because doing so transmits a confidential document to a third party. Reviewing is slow, detailed, unpaid work that AI is genuinely good at assisting with and reviewers are told they can't use it.

That conflict has no resolution today. Reviewers either break confidentiality quietly, or do without. ReviewGate resolves it architecturally instead of by policy: the manuscript never leaves the browser, and the agent only ever sees what the reviewer explicitly hands it.

What it does

Open a manuscript in .txt, .md or PDF and it is parsed and stored entirely in your browser's IndexedDB. The page exposes eight WebMCP tools to your agent. Every tool that would return manuscript prose pauses and asks you first, showing exactly which sentences are about to cross, with per-passage checkboxes. You release some, withhold others, or deny outright.

Every decision is written to an append-only disclosure log you can export as Markdown for the editor: which tool asked, why, how many passages were requested, released and withheld. "I used AI" stops being a confession and becomes an auditable statement.

Why this had to be WebMCP

Every other way of giving an agent access to a manuscript involves sending the manuscript somewhere. A server MCP has to transmit it. An API integration has to transmit it. Copy-paste transmits it.

WebMCP tools execute in the page, in the reviewer's own session, on data that is already in the browser. The agent operates on a document that never moves. Rebuilt as a server MCP this project doesn't get worse but stops being possible. The architecture is the confidentiality argument.

How I built it

React 19, Vite 8, Tailwind 4, IndexedDB via idb, and a lazily-imported PDF.js for text extraction. No backend, by necessity rather than convenience.

Design decisions worth naming:

Consent is the return value, not a notification. execute() is async, so the tool simply doesn't resolve until the human decides. No browser confirmation API is needed which also means nothing breaks as the spec moves.

A refusal reads as a refusal. Deny a request and the agent receives an explicit statement: "the reviewer declined to release any text; do not infer anything about the manuscript's content from this refusal." An agent that reads an empty result as absence of evidence would conclude a withheld section was blank. Verified live: it replies "I can't assess it yet: access to the Methods section was declined."

Graduated disclosure. check_stats_reporting returns the numeric values reported in a paper without the sentences around them which provides a lower-disclosure path for a common review task.

The tool surface follows application state. One tool is registered before a manuscript is open, eight after, one again on close.

Standing grants are scoped to the sentence. "Release for session" never silently widens to neighbouring passages, and grants die with the manuscript.

The claim is enforced, not promised. The deployed CSP sets connect-src 'none', so the browser refuses every outbound request the page could make. A live counter in the header shows the count, and it reads zero.

Challenges I ran into

requestUserInteraction() doesn't exist. Several third-party guides document it; Chrome's shipped imperative API doesn't have it. The whole design rested on a human-in-the-loop pause. It turned out not to be needed at all ,an async execute() already makes the agent wait and owning the dialog means owning what it shows.

Registering once is wrong. I checked for document.modelContext at mount. An embedding browser installs the API after first paint, so the check found nothing, registered nothing, and never retried, indistinguishable from the site not supporting WebMCP. It now watches for the API and registers when it appears.

The sentence splitter broke on decimals. A naive regex split "p<0.05" into "(p<0" and "05).", showing reviewers meaningless fragments in the consent dialog.

Embedded browsers block blob downloads silently. The export appeared to succeed while nothing reached the reviewer. Exports now render on screen with copy and download beside them.

Real documents don't look like test fixtures. My heading detection only understood ALL-CAPS headings, so a report using "Chapter 3 : Problem Statement" parsed into six wrong sections with the title page promoted to structure. It now handles numbered, chapter and roman-numeral headings, drops contents-page entries, and filters repeated running headers.

What I learned

That a permission model is more interesting than a capability. WebMCP's obvious use is letting agents do more; the more interesting question is what it lets you safely refuse. And that the failure modes worth engineering against are semantic, not technical, an agent misreading silence as absence would have been far more damaging here than any crash.

What's next

Per-section rather than per-sentence grants for long manuscripts, a "what the agent has seen this session" view distinct from the chronological log, and redaction; releasing a sentence with figures or identifiers masked.

Honest limitations

Claim detection is regex pattern matching, not semantic analysis; the tool descriptions tell the agent so. The network counter is a demo instrument while the real guarantee is the CSP plus having no server to send anything to. No journal endorses this tool; it helps a reviewer comply with policy and documents what happened.

Tested with 28 automated checks including a WCAG AA contrast audit, and verified live in both ChatGPT's built-in browser and stock Chrome via the WebMCP origin trial.

Built With

Share this project:

Updates

Submission history