Inspiration

I run QuillPrint, a print marketplace in India. Real shops, real orders, real paper.

Watching people use it, the same thing kept happening. Someone needs notes printed before an exam tomorrow. They know exactly what they want: cheap, readable, ready tonight. But the app asks them about duplex, pages per sheet, binding edge, which shop. Most people do not know what "2-up duplex" means. They pick something, and sometimes it comes out wrong, and by then the paper is already printed and paid for.

The gap was never the settings. It was that nobody should have to speak in settings.

What it does

You tell an AI agent what you actually want. It handles the translation.

It reads your uploaded document and current print setup, then offers four plans: Cheapest, Fastest, Best readability, and Eco. Each one shows the real sheet count and an estimated price from live shop rate cards, and it explains the trade-offs, including when packing pages tighter would make them hard to read. It compares real nearby shops on rates, capability and ETA. When you pick one, it applies the settings to the page you are looking at, so you watch your own draft change.

Then it stops.

There is no place_order tool and no pay_order tool. A test asserts they do not exist. The agent prepares everything and opens checkout, and confirming and paying stays with you. For something that uses real paper at a real shop, I think that boundary is the product, not a limitation.

How I built it

Nine imperative WebMCP tools registered from the top level page with document.modelContext.registerTool, all under src/webmcp/:

get_print_context, upload_print_documents, suggest_print_plans, compare_print_options, prepare_print_order, prepare_reorder, get_checkout_summary, get_order_status, open_checkout_review.

Things I cared about:

Strict schemas with additionalProperties false, enums and bounds. Allowlisted output, so no document contents, storage URLs, upload keys or tokens can leave the serializers. A draft_version guard so the agent cannot act on a draft that changed underneath it. AbortController lifecycle so tools tear down on sign out. Prices labelled as estimates, because the payable total is only ever the checkout screen's.

There is also a small panel on the page listing the registered tools, built from the actual registration result rather than a written down copy, so a tool that failed to register shows as failed.

Challenges

The hardest part was deciding what the agent should not be allowed to do.

Mixed colour is the clearest example. You can now say "pages 3, 7 and 12 to 14 in colour" and the agent applies it. But it cannot choose those pages for you, because it never sees your document's contents. It would be guessing, and a wrong guess bills you for colour you did not need, after it is printed. So it asks instead.

Pricing mixed colour correctly took real care too. Shops price black and white and colour separately, and a mixed job splits billed sheets between the two, where a sheet counts as colour if any page on it is colour. My first version priced every mixed sheet at the colour rate and quoted too high.

What I learned

Tools are a design surface, not just an API. Every field I exposed was a decision about what the agent is allowed to assume. The restraint mattered more than the coverage.

I also learned that an agent saying "I need to know something" is a feature. Ask for nearby shops without giving location and it asks you, instead of quietly ranking by something else.

What's next

Per page colour detection, so mixed colour becomes fully autonomous. The pipeline extracts page count and dimensions today but not colour. Once it does, you set the policy instead of the pages: colour only if more than five percent of the page is coloured. I did not ship it as a guess.

Built With

Share this project:

Updates

Submission history