The problem

Every small business collects files from people who are not their colleagues: monthly invoices from subcontractors, ID documents from new hires, photos from event participants. Those people do not have an account on your system and will not make one. So the files arrive by email, one at a time, named "scan001.pdf", and somebody spends their Friday renaming them into the right folder and ticking names off a list.

Ukebaco (https://ukeba.co) is the boring fix: you publish one link, anyone can upload without signing in to anything, and the file lands directly in your Google Drive™ — sorted into a folder tree you defined, renamed from the answers the sender typed, and recorded as a row in a Google Sheets™ ledger. It has been live and taking real payments since 2026-09-03.

What WebMCP adds

The tedious part was never the uploading. It is the two questions that bracket it:

  • Before: "set up a page to collect September invoices from my 12 subcontractors, PDF only, due the 30th, folder per company."
  • After: "has Acme sent theirs yet? what about the rest?"

Both are now sentences you say to the agent in the tab where you are already signed in. The dashboard registers four tools with document.modelContext.registerTool():

Tool What it does
create_link Creates the public upload page: fields to ask, folder tree, file-naming rule, deadline, PIN, allowed extensions. Returns the URL to share.
list_links The owner's links with status, destination folder and ledger URL.
search_submissions Who sent what, when, and the Drive URL of each file. Matches company name, folder path, receipt number, file name.
set_link_active Stops or resumes a link. The URL survives; submissions are refused.

create_link is the interesting one. Creating a good file request is a form with about fifteen decisions in it — that is precisely the shape of task people abandon halfway. Described in one sentence to an agent, it becomes one tool call. Folder templates, file-name templates and field types are inferred from a natural-language description and normalised in a pure, unit-tested layer before they ever reach the API.

search_submissions is the one people use every day. "Has this month's invoice come in from X" is a question the ledger can answer, but only if you open the spreadsheet and scroll. The tool answers it in place, and returns the Drive URL so the next sentence can be "open it".

Why WebMCP is the right fit — and not a remote MCP server

We built both, so this is an informed answer.

WebMCP let us add agent control without inventing a single new credential. execute runs inside the owner's signed-in tab, so fetch carries the existing session cookie and the server identifies the user with the same getSession() it already used. There is no token to issue, store, rotate, revoke or leak. Log out, and the tools go with the tab. If you are not signed in, the dashboard redirects to /login and no tools are ever registered. For a product where the whole value proposition is "your files stay in your own Drive", adding a new long-lived credential to reach that Drive would have been the wrong trade.

The second reason is that the server does not get more permissive because an agent is asking. Plan limits and paid features (PIN, deadline, email verification) are enforced by the same /api/links route as the UI; when the plan does not allow something it returns 402 and the tool relays that sentence verbatim to the agent. There is deliberately no "agent path" through the paywall.

We shipped a remote MCP server too (POST /api/mcp, with Ukebaco acting as its own OAuth 2.1 authorization server — dynamic client registration, PKCE, a consent screen and refresh-token rotation) so ChatGPT and Claude can reach the same four tools from outside the browser. It works, and it took roughly six times as much code as the WebMCP path, almost all of it in issuing and revoking credentials. That comparison is the strongest argument for WebMCP we can make: for a signed-in web app, WebMCP is the version of this feature you can actually maintain.

The part we deliberately kept human

Agents get the repetitive half. People keep the half that should stay deliberate.

  • Choosing an existing Drive folder stays in the Google Picker, which an agent cannot drive. Our OAuth scope is drive.file only, so Ukebaco can see the folders it created and the ones you personally handed it. An agent cannot widen that, and we did not add a tool that pretends otherwise.
  • Everything a submitter typed is marked untrustedContentHint. Submitters are third parties: the company name, the note in a text field and the file name are strings a stranger wrote. They are data to be shown, never instructions to be followed. This is the one place where an agent-readable file-collection service is genuinely dangerous, and it is annotated at the source.
  • readOnlyHint on list_links and search_submissions, so the agent knows which two of the four tools cannot change anything.
  • Calls from tools carry X-Ukebaco-Client: webmcp, and link creation records which route it came from. When an agent creates a link, the audit trail says so.

How we built it

Next.js 15 on Firebase App Hosting; Firestore for metadata only (90-day TTL); files go from the submitter's browser straight to Drive's resumable session URI and never touch our servers.

The WebMCP layer is split in two so the interesting part is testable:

  • src/lib/webmcp-tools.ts — tool definitions, JSON Schemas, and the normalisation of whatever the agent sends (labels in any language become valid field ids; folder templates are resolved against the fields that actually exist; a date becomes an end-of-day deadline). No I/O. Covered by unit tests.
  • src/components/WebMcpTools.tsx — registration and fetch. Feature-detects both document.modelContext and the older navigator.modelContext, unregisters on unmount, and if registration fails the dashboard carries on as an ordinary web page.
  • src/lib/link-service.ts — one decision layer shared by the dashboard API, the WebMCP tools and the remote MCP server, so the three routes cannot drift apart.

What is new in the challenge window

Ukebaco itself predates the challenge. Everything agent-facing — the four tools, the pure tool layer and its tests, the shared link service, the remote MCP server and its OAuth authorization server — was written on 2026-09-04, inside the submission period. See the commit history and ADR-0002 / ADR-0003 in docs/adr/.

Try it

Sign in at https://ukeba.co with any Google account. The free plan needs no payment details and gives you a working link. Open the dashboard in ChatGPT's in-app browser and ask it to make you a file request.

What's next

Per-tool consent that the owner can set once ("agents may search, but never create"), richer folder inference from a single sentence, and dropping the Chrome origin-trial header the day WebMCP ships unflagged.

Built With

Share this project:

Updates

Submission history