Disclosure: I'm the founder of Lulu Ads. This is the engineering story — the interesting parts are the constraints, and I'd rather you tear the design apart than clap.
The problem nobody is paid to solve
There are thousands of MCP servers right now — the tools that AI agents like Claude call to search, fetch, book, compute. Nearly all of them are free, run by individual developers, with real hosting bills and zero revenue. The tool layer of the agent economy has no business model. That's the gap I've been working on.
The obvious answer — "put ads in it" — is also the obviously terrifying one. Ads near LLM output is how you inherit every mistake ad tech ever made, at higher speed. So this post is about the constraint list, because the constraints came before the product.
The invariants
Before writing code, I wrote five rules the SDK must never violate:
- An ad must never break a tool call.
- An ad must never instruct the model.
- An ad must never be undisclosed.
- An ad must never appear on a failure.
- An irrelevant ad must cost the advertiser, never the user.
Everything else in the architecture falls out of these.
Invariant 1: fail-open, 150ms, no exceptions
The SDK wraps your MCP tool response. The ad fetch gets a hard 150ms budget. Timeout, network error, malformed response, backend down — doesn't matter: your response goes out exactly as your server produced it. The failure mode is "no ad", never "no answer". If our whole backend disappears tomorrow, integrated servers notice nothing.
This sounds trivial. It isn't — it means the ad path can never sit between your tool and your user. It's a side-channel annotation, not a proxy.
Invariant 2: the model is the gatekeeper
This is the design decision people push back on most, so to be precise: the sponsored unit is a data field, labeled sponsored, attached to the tool response. It is not a prompt. There are no display instructions anywhere in the SDK — not "show this", not "consider mentioning", nothing.
The host model sees a clearly-labeled sponsored suggestion next to the actual tool result and decides on its own whether it's relevant enough to surface. If it's junk, the model ignores it and the user never sees it.
That flips the usual ad-tech power dynamic: the publisher surface is smarter than the ad. A banner can't evaluate itself; a frontier model evaluating a labeled suggestion absolutely can.
Invariant 4: never on errors
If your tool call failed, the user is having a bad moment. The SDK refuses to attach sponsored data to error responses, at the middleware level. Monetizing failure states is how products start hating their users.
Why CPA and not impressions
Impression-based pricing rewards spraying ads everywhere and praying. Cost-per-action means the advertiser pays only when someone actually converts — so the only ads worth running are genuinely relevant ones. Incentive alignment beats policy enforcement.
What integration actually looks like
Python (FastMCP middleware — one line after setup):
from lulu_ads.middleware import LuluAdsMiddleware
mcp.add_middleware(LuluAdsMiddleware(
publisher_id=os.environ["LULU_ADS_PUBLISHER_ID"],
api_key=os.environ["LULU_ADS_API_KEY"],
))
TypeScript (server wrapper):
import { withLuluAds } from "lulu-ads";
const server = new McpServer({ /* ... */ });
withLuluAds(server);
Install is pip install lulu-ads or npm install lulu-ads; you register once at getlulu.dev/publishers to get credentials. 70% of revenue goes to the server owner. Adding it is free, and per invariant 1 it can't hurt your server even if it earns nothing.
What I'm still not sure about
Honest open questions, because pretending there are none would be marketing:
- Does model-as-gatekeeper hold up long-term? Today, host models are conservative about surfacing sponsored content. If models ever get tuned for engagement the way feeds were, this safeguard weakens. I don't control that; disclosure at the protocol level is the backstop.
- Context boundaries. The SDK tells publishers to exclude sensitive user data from ad context. That's a rule, and rules need enforcement tooling — that's on the roadmap.
- Host policies. Anthropic, OpenAI and friends will eventually have opinions about sponsored fields in tool responses. I think a disclosed, ignorable data field is the version that survives that conversation — it's why the design looks like this.
If you run an MCP server, I'd genuinely like your take — especially if your take is "never touching this, because X". The repo is at github.com/Lulu-The-Narwhal/lulu-ads, it's on the official MCP registry as io.github.Lulu-The-Narwhal/lulu-ads, and the docs live at getlulu.dev.
Top comments (0)