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.

v0.1 — beta. Live on npm today.

@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

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
});
OptionTypeRequiredDescription
publisherIdstringyesYour pub_xxx identifier from the dashboard.
apiKeystringyesBearer token. Keep server-side; never embed in client code.
transport"stdio" | "http"noAuto-detected from your MCP server config. Override only if you need to.
debugbooleannoLogs every fetchAd call and response to stderr. Default: false.
timeoutMsnumbernoNetwork 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

FieldTypeRequiredDescription
contextstringyesFree text that describes what the user is doing. Used for contextual matching.
formatstringnoPreferred ad format. Default: "native".
toolNamestringnoName of the MCP tool being called. Improves analytics.
userRegionstringnoISO 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_..." }
  }
}
Disclosure is non-negotiable.

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 codeMeaningWhat to do
BBX_AUTHInvalid or revoked API keyCheck dashboard → API keys; rotate if compromised.
BBX_RATE_LIMIT1k requests/minute exceededEmail support@boostboss.ai for a higher cap.
BBX_TIMEOUTNetwork or upstream timeoutNo-op; ad just won't render. Lumi never blocks your tool response.
BBX_NO_FILLNo matching advertiserExpected sometimes; not an error condition.
BBX_POLICYContent matched a policy blockCheck 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:

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.