LittleLibrary
Drop files onto an infinite plane. They file themselves by topic.
What inspired me
Most of the "AI organizes my files" demos I have seen end in a chat box. You dump a pile of stuff in, and it comes back as tags or a folder tree. You never actually watch a file land. You cannot walk the result. And if a second agent showed up, there would be nothing for it to share. It is not really collaboration. It is just a dump with extra steps.
WebMCP felt like a different idea. The page itself can be the thing an agent acts in. Not a remote API with a chat UI glued on. A site that registers tools, and a person who is still sitting there looking at the same wall.
I wanted that to feel obvious. Empty infinite plane, an intake pile, topic clusters you can pan around. You drop PDFs, notes, photos, videos, or try the samples that were given. An agent utilizing WebMCP reads the occupancy grid, picks a clip, and carries it to a cluster.
That is the whole thing. LittleLibrary is just that wall.
How I built it
The whole app lives in the browser.
- Next.js 16 (static export) and React 19
- Module stores for documents, camera, intake, and the agent.
- pdf.js for PDF text and first-page posters
- A Cloudflare Worker that serves the
out/folder
There is no login, and no server that "is" the library. Nothing is kept or stored.
On load, the page registers four site tools on document.modelContext: get_occupancy, place_tile, place_batch, and cluster_visible. The executes live in src/lib/tools.ts. Registration is in src/lib/webmcp.ts. The wall only reads state. The agent never imports the packer or the renderer.
The agent speaks the page language: clip ids, occupancy slot ids ("3,-1"), and topic keys. Never pixel x,y. I tried that once. Had to quickly pivot.
The built-in filing loop is:
get_occupancy
→ pick a pending clipId
→ reach the intake pile
→ grab the card
→ carry it to the packed destination
→ place_tile
→ repeat until the pile is empty
callTool uses the host when one is attached. If not, try the samples still runs the same executes. Occupancy slots are what the agent records. Humans act in the UI. Agents act with tools. Both land on the collaborative environment.
What I learned
Slots are a language. Pixels are a guess. place_tile({ clipId, slotId }) is something a model can actually use. (412, 880) is something it invents.
Occupancy and drawing are different jobs. The agent needs a grid that does not stack. The wall needs organic sections. If I make the agent speak packed coordinates, it starts importing the renderer, and then I have two sources of truth.
Tool success has to be visible. The hand shows WebMCP and the live tool name. You watch place_tile happen on a card, not in a log. If you cannot see it, it does not feel like the agent did anything.
The human has to stay first-class. Pan, drag, marquee, inspect. If the next get_occupancy cannot see what you did, you are just taking turns. That is not a shared wall.
WebMCP is the product surface here. If the host cannot see the tools, the agent cannot file. One write path for people, the built-in loop, and an attached model.
One store emit used to re-render the whole wall. Pan has to write a transform, not React state. Visibility has to be a spatial query, not a list of every tile.
Challenges I faced
My first initial idea was to give the agent a planner that knew about sections, anchors, and links. It looked smart on paper. But ended up tearing it back to four tools and a loop the page already understands.
Then the stage started lying.
The hand would skip to the slot instead of carrying the card there. I had to ease the camera with the card, re-read the destination every frame, and let a user pan cancel the camera without dropping the card on the floor.
On grab, the card in the hand was hidden behind the rest of the pile and the next cards slid up over it. The fix was a z-index, which is almost embarrassing, but the lesson stuck: pickup, carry, and drop are one action in three frames. If the layers disagree, the whole collaboration looks fake.
The pile itself fought me. If the agent always took the front card, every drop stayed in one topic. I shuffle samples, promote a pending clip, and prefer a different topic than the last place, so the hand actually travels between sections.
Performance showed up as soon as the wall had more than a handful of cards. A single snapshot subscription re-rendered every mounted tile on every agent phase. Camera motion went through React. Culling ignored the yaw, so we mounted five times what was on screen. I split the stores, pinned the world transform to a ref, and made visibility a grid query.
Exporting for Cloudflare was the last constraint. The app is client-only, so output: "export" is honest. A leftover route handler and generated icons had to become static or the build refused to write out/.
The hard part was never "register a tool." It was making the tools a language good enough that a person and a model can occupy the same wall, at the same time, and both know where the chowder recipe actually landed.
Built With
- nextjs
- typescript
Log in or sign up for Devpost to join the conversation.