-
-
One button, one autonomous pass over twelve open procurement notices. No further human input.
-
Seven tools registered through document.modelContext. Every agent call is mirrored on the right, in the human view.
-
Every criterion ruled met / gap / unclear with its evidence, four integrity flags with their reasoning, and a dated bid plan.
-
An agent raised the bid ceiling through update_supplier_profile and the board re-ranked live: this notice moved 17 to 41.
-
The board ranked by fit, with the reasoning trace beside it: every tool call, its output and its latency.
Inspiration
Across the EU roughly half of all public contracts attract a single bidder. The usual explanation is corruption. The more common one is arithmetic: a tender dossier runs 40 to 130 pages, the qualification criteria that decide whether a company may legally bid are scattered through it, and a 26-person firm has nobody who can read forty of those a month. So they stop reading, and the field empties out on its own.
The document that tells a supplier you qualify for this is the same document that reveals this one was written for somebody else — a specification fixed to 14 mm and 63.5 Wh describes exactly one laptop on the market. Once you are parsing dossiers at scale, both answers fall out of the same pass.
TenderLens does that pass. The WebMCP layer is what lets somebody else's agent do it too, on the same page, in front of the human it is working for.
What it does
One button starts an autonomous sweep. For every open notice the agent opens the dossier, extracts the qualification criteria into structured form, rules each one met / gap / unclear against the supplier's real profile with the evidence attached, screens the buyer's award history and the clause wording for integrity red flags, scores fit out of 100, and writes a dated bid plan working backwards from the deadline with an owner on every step.
It ends with a ranked board — 87 down to 17 across twelve notices — and a written bid / no-bid brief per notice. The full reasoning trace streams to the browser while it runs: every tool call with its input, its output and its latency.
The integrity screen is five detectors, each grounded in a documented single-bidding indicator: SHORT_WINDOW, THIN_COMPETITION, VENDOR_CONCENTRATION, SPEC_LOCK and INCUMBENT_LOCK. None is presented as proof of wrongdoing. Each carries the reasoning that makes it worth a clarification question or a formal challenge.
Why this use case is a strong fit for WebMCP
Bid/no-bid is a conversation, not a query. The questions a bid manager actually asks are open-ended and chained — what closes this week that we can actually win? which of those look rigged? what would we qualify for if our turnover were three million instead of one-point-eight? — and every answer changes the next question. That is exactly the shape of work an agent is good at and a filter UI is bad at.
But it is also work with consequences. A supplier who skips a tender because an agent said "weak fit" has lost a contract on the strength of a sentence. So the human has to stay in the room, looking at the same board the agent is reasoning over.
WebMCP is the only arrangement that gives you both. The tools live on the page the human is already looking at. The agent gets typed JSON — fit scores, verdicts with evidence strings, flags with rationale — instead of scraped DOM text, and every call it makes is mirrored into a panel on that page, in the human's field of view, while the board itself re-ranks in front of them. No API key, no OAuth handshake, no separate agent-facing copy of the product to drift out of sync.
How it creates a better user experience
Three things change:
- You ask, instead of filtering. "Show me everything closing inside ten days with an integrity flag" is one sentence. In the UI it is four controls and a sort.
- You can see what the agent did. Every tool call appears in the Agent activity panel with its arguments and a one-line result. The verdict is auditable in the moment, not after the fact.
- The page answers with the page.
search_noticesoutlines the matching rows on the board.update_supplier_profilere-ranks it live. The agent's answer and the human's screen are the same object, so there is nothing to reconcile.
What people and agents can do together that was difficult before
The what-if is the one that did not exist. update_supplier_profile lets the agent change the profile every notice is scored against — turnover, headcount, certificates, capability areas, bid ceiling — and the entire board re-ranks in the human's browser while they watch. Ask "what would an ISO 27001 certificate be worth to us?" and you do not get an estimate; you get the actual board you would have had, recomputed by the real engine, with the notices that moved outlined.
That is a joint action. The agent supplies the question and the argument, the page supplies the ground truth and the recomputation, and the human sees both at once and decides. Before WebMCP you got one or the other: a chatbot that could reason but only about text you had pasted to it, or a dashboard that held the truth but could not be asked anything.
The same applies to the integrity work. A journalist can now walk an agent across a portal — screen these, tell me which buyers concentrate awards — and every claim the agent makes is anchored to a screen_integrity call whose output is sitting on screen next to the claim.
How I implemented WebMCP
Seven tools registered through document.modelContext.registerTool, in edge/webmcp.ts:
| tool | what it does |
|---|---|
search_notices |
rank open notices by fit; filter by category, country, days left, fit band, or integrity flags |
get_notice |
one notice in full: every criterion with its verdict and evidence, flags, bid plan, optional raw dossier |
screen_integrity |
the five single-bidder detectors with the reasoning behind each flag |
get_bid_plan |
dated plan worked backwards from the deadline, owner on every step |
get_supplier_profile |
the profile every notice is scored against |
update_supplier_profile |
change the profile and re-rank the board — the what-if tool |
run_sweep |
the whole pass over every notice, returning the ranked board |
Each registration carries a real JSON Schema — enums, descriptions and required fields on every property — because the schema is the only instruction the visiting agent gets. Every execute returns { content: [{ type: "text", text: JSON.stringify(...) }] }, so the shape holds across clients.
Three implementation decisions are worth naming:
- Tools call the product, not a demo path. Each
executehits the same/api/notices,/api/notice/:idand/api/profileendpoints the page itself uses. There is no agent-facing copy that can drift out of sync with what the human sees. - Every call is visible.
record()writes each invocation — name, arguments, one-line result — into the Agent activity panel on the page. Tool calls also act on the view:search_noticesoutlines matching rows,update_supplier_profileandrun_sweepre-rank the board. - Writes are validated, not trusted.
update_supplier_profilegoes throughapplyProfilePatchinedge/agentapi.ts, which range-checks every field and ignores anything it does not recognise. An agent can explore the profile space; it cannot corrupt it.
Registration degrades cleanly. If document.modelContext is absent the pill reads "WebMCP not detected" and the page works as an ordinary web app. The tools are also exposed on window.__TL_TOOLS__, and every tool in the list has a run button beside it that fires the identical execute() — so a judge without a WebMCP-enabled browser can still see each tool run and log its result.
How I built the rest
TypeScript throughout. Supabase Postgres holds the notices, the supplier profile, the run log and every assessment; Server-Sent Events carry the live trace; the agent itself runs as a Supabase Edge Function on Deno. The front end is a single static page on GitHub Pages that talks to that function over CORS-open JSON and SSE, which keeps the page a plain document a judge or an agent can open with nothing in the way.
The agent core — orchestrator, six tools, provider adapters, types — depends on nothing from Node, React or Next. That buys a second runtime for free: the same engine drives a Next.js build and the Deno edge build, unmodified.
The decision worth defending is deterministic core, model at the edge. Every score, verdict and flag comes from code that can be read, tested and cited. A supplier disqualified on a criterion needs to point at the rule that fired; a score that exists only inside a model's context window cannot survive a procurement review board. The side effect is that the hosted demo runs with zero API keys — which is also why a judge can open it, and a visiting agent can use every tool, without anyone configuring anything.
Graceful degradation at every boundary: no database falls back to the bundled corpus, Supabase unreachable mid-request falls back silently, a deep link to a never-assessed notice is assessed on demand server-side.
What is new in this window
The procurement engine was built 24–31 August 2026. The WebMCP layer — edge/webmcp.ts, the JSON API in edge/agentapi.ts, the /api/notices, /api/notice/:id and /api/profile routes, the Agent tools card and the Agent activity panel — was added on 31 August and 1 September 2026, inside the submission window. The repository was published fresh for this submission on 1 September, so its commit timestamps all fall on that date; the split is by file instead, and it is exact. Those files are the new work and nothing else in the repository was touched for this hackathon.
Governance and compliance
- Auditability by construction. Every assessment is a row tied to a run, carrying the verdicts, flags, checklist and reasoning mode used. You can reconstruct why the agent said what it said, months later — including every call an agent made through WebMCP.
- Explainable decisions, not scores. Each verdict ships with its evidence string ("Requires EUR 600,000; profile turnover is EUR 1,850,000"). Each risk flag ships with its rationale.
- Row-level security on all four tables. The demo project's open policies exist because it holds nothing but synthetic notices; a real deployment scopes them to the authenticated supplier.
- No unsourced claims. The integrity screen surfaces the pattern and why the pattern matters — the standard a challenge before a procurement review body is held to.
Who pays for this
Two customers, one engine.
Suppliers — SaaS subscription (primary). Solo EUR 49/mo: one country, weekly digest, three profiles. Team EUR 199/mo: three countries, daily sweep, unlimited profiles, bid plans exported to calendar. Agency EUR 699/mo: white label, API access, multi-client dashboards for bid-writing consultancies. The wedge is measurable: a firm bidding six times a year at roughly five days of unpaid bid effort each spends about EUR 12,000 of staff time. TenderLens removes the two days spent deciding whether to bid, and kills the disqualified-on-a-technicality bids entirely.
Watchdogs and journalists — usage-based. Integrity screening billed per thousand notices analysed, NGO tier at cost. Anti-corruption organisations and investigative desks already do this by hand, one buyer at a time — and WebMCP is what turns that into a conversation with an agent instead of a spreadsheet.
Buyers — compliance licence (later). The same detectors run pre-publication: a procurement office checks its own draft notice for SPEC_LOCK and SHORT_WINDOW before it goes out. Sold per authority, annually.
Marginal cost is a few cents of model spend per notice, and zero when the deterministic engine handles it.
Challenges I ran into
Designing tool descriptions turned out to be the hard part, not wiring the calls. The first pass named the tools after the endpoints and the agent used the wrong one constantly. Rewriting each description around the question a user would ask — "use this to answer what should we bid on, what closes this week" — fixed it without touching a line of the implementation.
Scoring calibration was worse. The first scorer put eight of twelve notices between 90 and 100, which ranks nothing. Fit is now a five-factor blend — qualification, capability match, economics against the bid ceiling, historical competition, and slack between the deadline and the work the dossier implies — and the board spreads properly.
Two bugs only surfaced when I screenshotted the thing: the bid plan was scheduling steps in the past whenever a deadline was closer than its fixed offsets, and a delivery-time commitment was being marked "met" because the supplier's category matched. Both are fixed; both were invisible in the happy path.
Accomplishments I'm proud of
That the agent and the human are looking at the same object. The board an agent re-ranks through update_supplier_profile is the board on the human's screen — not a rendering of the agent's answer, the thing itself. Getting to that, rather than a chat pane bolted next to a dashboard, is what the whole WebMCP layer was for.
And the zero-configuration demo: it runs, fully, with no key, no database and no signup.
What I learned
That the tool schema is the product surface. An agent never sees the layout, the copy or the colours — it sees seven names, seven descriptions and seven schemas, and that is the entire product as far as it is concerned. Writing those with the same care as UI copy changed the agent's behaviour more than any change to the engine did.
And that "agentic" is not a synonym for "the model decides everything". The parts of this that make it worth deploying — the tool contract, the detectors, the trace — are engineering. The model is replaceable, and the system is better for it.
What's next
A live connector per portal — portalSearch is one function; TED first, then the national portals. A real dossier reader, with a PDF pipeline feeding the same Requirement[] shape. Scheduled sweeps with a morning digest. And more detectors — each one is an independent function of the notice, so it is purely additive.
The notices in this deployment are synthetic dossiers modelled on the structure of real EU and Western Balkans procurement publications. No real notice, buyer decision, supplier name or award record is reproduced.
Built With
- ai-agents
- deno
- html
- javascript
- model-context-protocol
- postgresql
- server-sent-events
- supabase
- typescript
- webmcp
Log in or sign up for Devpost to join the conversation.