Inspiration

PaperPup started as a personal itch, not a hackathon idea. It was needed to manage a real research library. There were real-world stakes in turning a recognized pattern of investigatory practices into a structured and AI-instrumented process.

The frustration that actually drove the design: ask a general AI chatbot to help with research, and it happily answers from the whole internet, or from memory, or both — not from the specific papers you actually collected and have deemed useful. Drafts as well that needed controlled tests run against them against the literature that supported the research. Anyone who has followed a chatbot down a research rabbit hole and landed somewhere completely off-target knows the feeling. PaperPup was built to make that structurally impossible, not just less likely.

WebMCP entered the picture partway through, and it fit almost too well. PaperPup already had a strict internal discipline — Library, Stack, and Room, each granting a narrower set of capabilities than the last with clear demarcation between them in the form of hand-off briefs. WebMCP was a way to expose any of that to an external agent with the demarcation points mapped to WebMCP site tools by context, and it was a natural fit. The hackathon became the deadline attached to work already in motion, not the reason the work started.

What it does

PaperPup is a personal, single-tenant research library. It is not a shared or social platform, and is organized around three deliberately narrowing contexts:

  • Library — search, add, and organize the papers you've actually collected.
  • Stack — pull a working set of papers together and investigate them with an AI, grounded only in that set.
  • Room — promote a mature stack into a frozen, bounded investigation. The corpus locks at that point — nothing added, nothing removed, except through one specific human-approved channel. WebMCP added a twist to that because now the human had an AI agent on their side that, unlike the agents that PaperPup sandboxes and uses to pit literature against literature, this new agent was the user.

Inside a Room is a bit more locked down for all because an investigating agent has constraints that make it genuinely useful. It can read the papers, follow a citation to a source that should be there but isn't yet, and propose adding it. What it can't do is add that source itself, or search outside the frozen set to find something else instead. There's no tool call that does either of those things registered in that context — not a rule the agent is trusting to follow, a door that structurally doesn't exist.

WebMCP is what lets any AI agent someone already uses — ChatGPT, or any other WebMCP-capable client — work inside this system directly, live, in the same browser tab and session, rather than through a bespoke chat widget bolted onto the page. That enabled bring your own agent; PaperPup supplies the grounding.

How we built it

PaperPup is a Next.js app backed by PostgreSQL, with an adapter layer pulling papers from Zenodo, OpenAlex/Unpaywall, arXiv, bioRxiv, and CORE, plus citation resolution against PubMed and Crossref.

The WebMCP layer lives entirely under one subfolder, src/webmcp/: a declarative manifest.js listing every tool's name, schema, and which contexts register it; register.js, which walks that manifest and calls the real document.modelContext.registerTool() API, managing one AbortController per tool so the whole set can be cleanly unregistered and re-registered on every navigation; envelope.js, which maps the current context — library, stack, room, or nothing selected yet — to the tools that should be live; and one thin handler per tool. Every handler reaches application logic only over HTTPS, never a direct import of server-only code — a boundary now enforced by an automated test that walks the whole import graph and fails on any reach into the backend and database layer.

Tools scoped to one specific stack or room don't take that stack or room id as a parameter - it is read directly from the page's own state since a tool visible only inside one specific room already proves which room that is. Asking the calling model to name it back would just be redundant. The user's agent translates both ways for the user to have flexibility in identifying resources in prompts and reading the responses after their agent has formatted the PaperPup response to present it however that agent knows their user likes to communicate.

We tested against a real host end to end — ChatGPT's WebMCP-enabled models — and, for fast local iteration before and alongside that, against @mcp-b/webmcp-polyfill, a real implementation of the same browser API with a dev-only testing shim for invoking tools directly with no AI client connected at all.

Claude (architecture and design) and Claude Code (implementation and live verification) were genuine build partners throughout — a fitting way to build a project about grounding external-to-system AI agents in trustworthy work.

Challenges we ran into

A permission model that could actually be violated in code, even after we thought we'd designed it out. Early on, a Room-context tool for reading a paper's metadata took a global paper id. That meant there was nothing stopping an agent from simply naming a paper outside the room's frozen set and getting it back anyway. The interface didn't expose search, so it looked safe. The fix was structural, not a permission check: that tool now takes a position within the room's own paper list, not a global id. There's no way to even construct a request for something outside scope, because the parameter itself can't express it. This ended up being the single most important architectural decision in the whole submission — a stronger guarantee than any runtime check could offer.

A crash that taught us to build a permanent test, not just fix the bug. One early handler reached for a server-only database module directly instead of going through an HTTP route, and it pulled pg straight into the browser bundle — crashing the app outright the moment that code path loaded. We fixed the immediate bug, then built an automated test that walks the entire import graph under the WebMCP layer and fails on any reach into server-only code, so the same mistake can't happen again silently.

Getting genuine live verification, not just something that looked right in code. ChatGPT's WebMCP support turned out to depend on which underlying model was selected. One variant has it disabled entirely, which looks identical to a bug in your own tool descriptions if you don't know to check. The in-app browser also has no visible developer console by default, so confirming what was actually happening required either asking the model to report it back, or falling back to a real polyfill running locally for a tighter dev loop. Turns out, this was quite useful to see and gave us insights in how to structure the descriptions in our WebMCP site tool descriptions that allow the user's agent to learn the capabilities impressively well.

Realizing a two-dimensional problem was actually a one-dimensional one. A room's frozen corpus can span multiple stacks, which made stack-plus-position addressing feel necessary at first. Checking it against an existing invariant — that a room's assigned stacks are frozen and can't change after assignment — showed a flat position across the room's whole corpus was just as safe and considerably simpler. The extra complexity wasn't buying anything.

Accomplishments that we're proud of

That the room-envelope guarantee is real, not asserted. An agent working inside a frozen investigation cannot search or read past its bounds. This can be confirmed by trying, not just by design intent, and it is immensely useful when running control tests whereby a standing agent must defend a corpus using only what is in that room. The only out is that the room's agent can propose a source, such as a citation reference that it wants the full text for, and the user must then intervene to approve whether that visibility will be allowed. Configurable of course, but that is a sizable document in itself to describe the capabilities envisioned and being built.

That we built the discipline to actually verify claims rather than trust that code looked right: a manifest-invariants test that caught its own first-draft bug immediately; a boundary test proven with a deliberate red-then-green check rather than accepted on a single clean pass; a live investigation's output cross-checked word-by-word against the source papers to confirm it wasn't quietly blending in outside knowledge.

That the two-way collaboration model — an agent can discover and propose, a human retains final authority, and neither side can silently override the other — came out of the architecture cleanly, rather than needing to be bolted on as an afterthought.

And that a cold-start ChatGPT conversation, with zero prior context about this system, correctly orchestrated a full multi-step workflow — select papers, form a stack, name and assign a room — narrating each step against real state as it happened.

What we learned

The strongest safety property isn't a rule a model is asked to follow. Rather, it is an action that's structurally impossible to request in the first place. A permission check can have a bug. A tool that was never registered, or a parameter that can't express an out-of-scope request, doesn't need to be checked at all.

Good architecture pays forward. PaperPup's Library/Stack/Room discipline existed before WebMCP was ever in the picture, built for reasons that had nothing to do with any hackathon. When WebMCP arrived, it mapped onto that existing structure almost exactly. That proved the discipline was sound on its own terms, not invented to fit a submission.

Real testing against a real host finds things no amount of code review does — a model variant with the feature quietly disabled, a browser with no visible console, the actual gap between what a tool's description says and what a cold model infers from it unprompted.

What's next for PaperPup

Extending full-text coverage further into the humanities gap through CORE's document-following path, alongside a search-external-and-add capability for the agent side — built with the same read-then-propose split that already governs how a Room accepts new sources, rather than a single tool that could add something directly.

Deeper investigation feedback: real progress detail while a grounded investigation is running, instead of a bare status flag, so a visiting agent has something concrete to narrate rather than silence.

Meta investigation: adding support for repositories that are outside the mainstream and not mentioned here, as well as an algorithm to expose mechanisms in full text in a weighted graph model toward providing pathway, compartment, and mechanism traversal similar to what MeSH provides but based on the methods and mechanisms presented by each full text that was collected.

Extending the same context-scoped discipline to Digest, the one surface that doesn't have a WebMCP presence yet — deferred earlier specifically because it wasn't needed for this demo, not because it's architecturally hard. The pattern that makes Library, Stack, and Room work is general; Digest is the natural next context to prove that again.

Built With

Share this project:

Updates

Submission history