Lumi SDK for MCP
Add three lines to any MCP tool handler. Sponsored content gets appended to your tool's response and rendered natively by the MCP host (Claude Desktop, Cursor, Cline, Continue, Zed, Windsurf). Time to first ad: under 10 minutes.
@boostbossai/lumi-mcp@0.1.0 is published — npm install @boostbossai/lumi-mcp resolves and works against the live Boost Boss MCP backend. Try the sandbox flow with publisherId: "pub_test_demo" and apiKey: "sk_test_demo" — no signup required to verify integration end-to-end. The full demand side (real advertiser inventory) is in early beta; expect lower fill rates than mature ad networks until our Founding Publisher cohort fills out. Email hello@boostboss.ai to join.
Prerequisites
- A Boost Boss publisher account — sign up if you haven't.
- An MCP server that exposes at least one tool.
@modelcontextprotocol/sdk≥ 1.0 supported; older MCP server libraries work but aren't actively tested. - Node.js ≥ 18 (or any runtime that supports
fetch). - Bearer token from your publisher dashboard (settings → API keys).
Installation
# npm npm install @boostbossai/lumi-mcp # pnpm pnpm add @boostbossai/lumi-mcp # yarn yarn add @boostbossai/lumi-mcp
The package ships with TypeScript types and works with both stdio and streamable-http transports.
Initialization
import { LumiMCP } from '@boostbossai/lumi-mcp'; const lumi = new LumiMCP({ publisherId: process.env.BBX_PUBLISHER_ID, apiKey: process.env.BBX_API_KEY, transport: 'stdio', // 'stdio' | 'http' (auto-detect by default) debug: false // log every ad request to stderr });
| Option | Type | Required | Description |
|---|---|---|---|
publisherId | string | yes | Your pub_xxx identifier from the dashboard. |
apiKey | string | yes | Bearer token. Keep server-side; never embed in client code. |
transport | "stdio" | "http" | no | Auto-detected from your MCP server config. Override only if you need to. |
debug | boolean | no | Logs every fetchAd call and response to stderr. Default: false. |
timeoutMs | number | no | Network timeout per request. Default: 2500. |
Core API
lumi.fetchAd(opts)
Fetches a sponsored content payload contextually matched to opts.context. Returns null if no fill (no advertiser matched, frequency cap hit, etc.) — your tool should still return its primary output normally.
const ad = await lumi.fetchAd({ context: 'stripe customer creation', // the user's intent or tool topic format: 'native', // 'native' | 'banner' | 'inline' toolName: request.params.name // optional, used for analytics }); if (!ad) { return { content: result }; // no fill — return primary output only }
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
context | string | yes | Free text that describes what the user is doing. Used for contextual matching. |
format | string | no | Preferred ad format. Default: "native". |
toolName | string | no | Name of the MCP tool being called. Improves analytics. |
userRegion | string | no | ISO region (US, EU, APAC). If omitted, inferred from request. |
ad.toMCPBlock()
Converts the ad payload to an MCP content block ready to append to your tool's content array. Handles disclosure label, image rendering, and click-tracking link transparently.
return { content: [ ...result, ad.toMCPBlock() // appends a sponsored block, never replaces ] };
lumi.trackImpression(ad)
Fires the impression beacon. Called automatically by toMCPBlock() — only invoke manually if you're using a custom rendering path.
lumi.trackClick(ad)
Fires the click beacon. The click URL embedded in toMCPBlock() already redirects through Boost Boss for tracking — only invoke manually if you build your own link.
lumi.trackConversion(ad, conversion)
Fires a conversion event tied to a previously-served ad. Use when the user takes the action your campaign was bidding on (signup, purchase, tool invoke, lead). The conversion shares the auction context with the original impression so attribution and CPA billing work end-to-end.
// Inside any tool handler, after the user completes the desired action:
await lumi.trackConversion(ad, {
type: "signup", // matches campaigns.conversion_event_types
value: 29.99, // USD; optional but required for ROAS
currency: "USD", // ISO 4217; defaults to USD
externalId: "order_98123", // optional — your CRM/order id
});
Fire-and-forget — never throws. Failures emit an error event on the SDK and are surfaced via the silent-failure observability endpoint (/api/stats?type=recon).
Rendering the ad
You don't render anything yourself. toMCPBlock() returns a structured MCP content block, and the host (Claude Desktop, Cursor, etc.) renders it natively in whatever style fits its UI. The block looks roughly like this on the wire:
{
"type": "text",
"text": "\n\n— Sponsored —\nStripe Atlas helps you launch a US company...",
"_meta": {
"boostboss": { "impressionId": "imp_...", "adId": "ad_..." }
}
}
The "— Sponsored —" prefix is part of every block. toMCPBlock() bakes it in; do not remove it. Stripping the disclosure is a policy violation that suspends payouts.
Event handling
The MCP integration is server-side, so there's no onClick/onClose like the JS snippet — clicks are tracked via redirect URL, impressions via the toMCPBlock() render. If you want server-side hooks for analytics:
lumi.on('impression', ({ adId, advertiserId, cpm }) => { metrics.increment('lumi.impressions'); }); lumi.on('no_fill', ({ context }) => { console.debug('no fill for context:', context); }); lumi.on('error', ({ code, message }) => { metrics.increment('lumi.errors', { code }); });
Error handling
fetchAd() never throws on the bid path — network failures, timeouts, and policy rejections all resolve to null. Your tool's primary output is never delayed by Lumi.
| Error code | Meaning | What to do |
|---|---|---|
BBX_AUTH | Invalid or revoked API key | Check dashboard → API keys; rotate if compromised. |
BBX_RATE_LIMIT | 1k requests/minute exceeded | Email support@boostboss.ai for a higher cap. |
BBX_TIMEOUT | Network or upstream timeout | No-op; ad just won't render. Lumi never blocks your tool response. |
BBX_NO_FILL | No matching advertiser | Expected sometimes; not an error condition. |
BBX_POLICY | Content matched a policy block | Check dashboard → policy audit log for the rule that fired. |
Testing
Use a sandbox publisher ID in development — every ad request gets a fixed test creative back, no real demand-side calls happen, and impressions never accrue revenue.
const lumi = new LumiMCP({ publisherId: 'pub_test_demo', apiKey: 'sk_test_demo', debug: true });
The sandbox always returns a creative (no null), so you can exercise toMCPBlock() rendering paths reliably. Switch to your live publisher ID + API key before going to production.
Going live
Production checklist before flipping to live keys:
- Replace sandbox
publisherIdandapiKeywith the live values from your dashboard. - Confirm Stripe Connect is set up (no payouts without it).
- Set
debug: falsefor production — avoids stderr noise on cold start. - Run one live tool call end-to-end and verify the impression appears in your dashboard within 30 seconds.
- Optional: email hello@boostboss.ai with the MCP server's listing URL so we can add it to our supply-side allowlist — speeds up advertiser approvals. Self-serve seller submission via the dashboard is on the roadmap; for now this is a manual step.
Troubleshooting
fetchAd always returns null.
Check three things in order: (1) is your apiKey live (not sk_test_) when expecting real fills, (2) does the context string have any actual content (empty contexts get auto-rejected), (3) check the dashboard policy audit log — your account may be in review.
The host doesn't render the sponsored block.
Some MCP hosts collapse adjacent text blocks. If toMCPBlock() output is concatenated into your tool's text, the host may render it as one long string. Add a separator block ({ type: 'separator' } on supported hosts) between your output and the ad, or return them as distinct content array entries.
Disclosure label appears in a different language than my user.
Pass userLanguage to fetchAd — Lumi serves disclosure in 40+ languages but defaults to en.
I'm getting BBX_RATE_LIMIT at low volume.
The 1k/minute cap is per-publisher; if you're behind a load balancer with many MCP server instances, all share the same cap. Email support to raise.
I want to disable Lumi for a specific tool.
Just don't call fetchAd in that tool's handler. Lumi is opt-in per tool; nothing is auto-injected.
Changelog
See the API reference changelog for SDK release notes and protocol changes.