Inspiration
Lulu Ads builds the commercial infrastructure for the agent economy: disclosed, fail-open sponsored recommendations that surface inside real AI tool calls, already live in production across ChatGPT Apps, Claude Connectors, and CopilotKit. WebMCP -- a web page handing an AI agent real, callable tools directly via document.modelContext.registerTool, no separate MCP server required -- is the newest surface for that same idea. We built this project to answer one question directly: can a real ad live inside a real WebMCP tool result, stay honestly disclosed, and still feel like a genuinely useful recommendation instead of a banner bolted onto the page? Trip planning (weather + flights) is a theme any judge reviewing dozens of submissions understands in five seconds, which made it the right stage for that question.
What it does
Two real WebMCP tools, registered directly on the page -- and every single call is real Lulu Ads inventory:
- get_weather -- real current conditions for any city (Open-Meteo), paired with a "Sponsored" travel.activities recommendation (things to do at the destination)
- search_flights -- deterministic flight search between two airports, paired with a "Sponsored" travel.insurance recommendation (trip protection)
Both sponsored categories were live-verified by querying the real Lulu Ads network before being wired in -- this is not simulated ad data. Every sponsored result is clearly labeled "Sponsored," fetched through a same-origin backend route so the publisher API key never reaches the browser, and fails open to nothing at all if the ad network is slow or down -- a user never sees a broken page because an ad didn't load.
Open the manual demo in a WebMCP-capable agent and it calls both tools -- and requests both sponsored slots -- directly, no screenshots, no clicking. Open it in a regular browser and the same execute() functions power manual "Run" buttons, so nothing about the ad path is mocked for the demo. Tulip Trips (/demo) is the same two tools and the same two sponsored slots, driven by a real Gemini function-calling loop instead of buttons -- ask "what's the weather in Bangkok and find me a flight from TLV to BKK" and watch it decide when to call each tool, mention the sponsored offer naturally in its own reply, and close the loop with a trip-summary card.
How we built it
Vanilla HTML/CSS/JS on the frontend -- no framework, no bundler, safe DOM construction throughout, since sponsored ad text is real third-party advertiser content and never goes through innerHTML. A plain Node http server in TypeScript is the backend, deployed as a container on GKE. The chat demo uses the Vercel AI SDK (generateText + tool()) with Gemini, calling the exact same tool AND sponsored-slot logic the manual demo uses -- zero duplication. The ad integration itself runs through the lulu-ads SDK with a strict client/server split: the browser-side tool code calls a same-origin route, and only a server-side LuluAds instance ever holds the real publisher API key.
Challenges we ran into
The most interesting bugs we hit were ad bugs, not WebMCP bugs. A fresh server's very first request to the ad network could occasionally miss its timeout budget and silently show no ad -- correct fail-open behavior, but not what we wanted on a cold demo. Root-caused to a new connection object being created per request, throwing away the SDK's own warm-connection tracking every time; fixed by sharing one instance across requests and warming it up before the server accepts traffic. On the UI side, our first pass rendered the sponsored content as its own floating card stacked under the result -- two boxes where a real MCP widget should read as one -- so we merged it into a single card, with the sponsored content as a footer band of the same widget.
Accomplishments that we're proud of
A monetization layer that fails open by design and two sponsored categories live-verified against a real ad network instead of assumed -- get_weather and search_flights both carry real, disclosed Lulu Ads inventory, not a single hardcoded demo ad. And a manual demo and a full Gemini-powered chat assistant that share the exact same tool logic AND the exact same ad logic, with zero duplication between them.
What we learned
WebMCP's registerTool is refreshingly small surface area -- name, description, JSON schema, execute() -- and because it's just a function running in the page, the exact same code path serves a manual UI and an agent with no fork in the logic, including the sponsored-slot call inside it. The harder design problem wasn't the protocol, it was making a sponsored recommendation feel like a natural part of a real result instead of an ad bolted onto one -- both in language (how the chat assistant discloses it) and in pixels (how the card renders it).
What's next
More tools (hotels, currency conversion) means more real Lulu Ads inventory categories to plug in, broader host testing as more WebMCP-capable agents ship, and richer sponsored categories as the underlying ad network grows.
Built With
- docker
- gemini
- google-cloud
- kubernetes
- node.js
- open-meteo
- typescript
- vitest
- webmcp
Log in or sign up for Devpost to join the conversation.