Lulu Ads — Publisher Concierge について
Lulu Ads — Publisher Conciergeは、MCPサーバーやエージェントのツール結果に、明示されたスポンサー情報を追加するためのサービスです。SDKと連携機能でパブリッシャー登録や任意のスポンサー項目の挿入を支援し、表示するかどうかはホストモデルに委ねます。
主な機能
- 01結果へのラベル付きスポンサー項目の追加
- 02PythonおよびTypeScript SDK
- 03MCPサーバーとエージェントフレームワーク向け連携
- 04任意のスポンサーウィジェット表示
- 05サブスクリプション階層に応じた広告制御
活用シーン
- 01MCPツールの結果へのスポンサー情報追加
- 02パブリッシャー登録と広告枠の設定
- 03無料プランでは広告を表示し、有料プランでは無効化
MCP 対応
本サービス自体をMCPサーバーとして利用でき、パブリッシャー登録と連携方法を案内します。SDKとミドルウェアはMCPツールの結果にスポンサー情報を追加し、対応ホストでは描画ウィジェットも提供します。
設定
リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。
https://ads.getlulu.dev/mcpclaude mcp add --transport http lulu-the-narwhal-lulu-ads https://ads.getlulu.dev/mcp{"mcpServers":{"lulu-the-narwhal-lulu-ads":{"type":"http","url":"https://ads.getlulu.dev/mcp"}}}Cursor で開く{"servers":{"lulu-the-narwhal-lulu-ads":{"type":"http","url":"https://ads.getlulu.dev/mcp"}}}VS Code で開く{"mcpServers":{"lulu-the-narwhal-lulu-ads":{"type":"http","url":"https://ads.getlulu.dev/mcp"}}}{"mcpServers":{"lulu-the-narwhal-lulu-ads":{"serverUrl":"https://ads.getlulu.dev/mcp"}}}コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。
ツール
5| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
| framework | string | いいえ |
詳細
lulu-ads
エージェント経済のマネタイズレイヤー。
ラベル付きのスポンサー表示を1行追加するだけで、MCP サーバーやエージェントツールを収益化できます。
クイックスタート · インテグレーション · 対応ホスト · 対応サーフェス · stdio サーバー · 保証事項 · API 契約 · ホスト型ドキュメント · ブログ · パブリッシャーになる
パブリッシャーに70%還元 · CPAのみ · 800msでフェイルオープン · 設計上、プロンプトインジェクションはゼロ
↑ レンダリングされたスポンサー表示のライブ例 — 実際にローテーションする広告が表示され、約60秒ごとに更新されます。掲載が承認された場合は、どのプロジェクトでもこの表示をREADMEに埋め込めます。
Lulu Adsは、ツール自身の結果に、開示済みのラベル付きデータフィールドを追加します。 ホストモデル(Claude、Cursor、その他のエージェント)は、それを表示するだけの関連性があるかどうかを自ら判断します。 表示するよう指示することはありません。
| SDKが提供するもの(データフィールド) | ホストが表示するもの(ホストの判断) |
|---|---|
|
|
手間をかけずに始められます。MCP サーバーを追加すれば、あとはエージェントに任せるだけです。
claude mcp add --transport http lulu-ads https://ads.getlulu.dev/mcp
サーバーを収益化して
エージェントがスタックに合ったインテグレーションガイドを取得し、(お客様の同意を得て)パブリッシャー登録を行い、1行のコードを組み込み、広告枠が公開されたことを確認します。
表示されてクリックされると、CPA収益の70%を獲得できます。表示されなければ、料金は発生せず、何も壊れません。
プロンプトインジェクションはありません。データフィールドを提供し、ホストが判断します。
クイックスタート
Python
pip install lulu-ads
# or: uv add lulu-ads
# or: poetry add lulu-ads
from lulu_ads import LuluAds
ads = LuluAds(publisher_id="pub_123", api_key="lk_...")
result = search_flights("TLV", "BKK", dates)
result["sponsored"] = await ads.sponsored_slot(
context={"tool": "search_flights", "category": "travel.flights"},
)
return result
FastMCPサーバーなら、1回の呼び出しで利用できます。認証情報は環境から取得され、すべてのツール(現在と将来の両方)に、通常のsponsoredデータフィールドと、対応するホスト(例:Claude.ai)で表示されるスポンサーウィジェットの両方が自動的に追加されます。
export LULU_ADS_PUBLISHER_ID=pub_123
export LULU_ADS_API_KEY=lk_...
from lulu_ads.enable import enable_lulu_ads
enable_lulu_ads(mcp, endpoint_url="https://my-server.example.com/mcp")
ウィジェットは不要で、データフィールドだけを使いたい場合は、通常のミドルウェアだけでも動作します。
mcp.add_middleware(LuluAdsMiddleware())
TypeScript
npm install lulu-ads
# or: pnpm add lulu-ads
# or: yarn add lulu-ads
# or: bun add lulu-ads
import { LuluAds } from "lulu-ads";
const ads = new LuluAds({ publisherId: "pub_123", apiKey: "lk_..." });
result.sponsored = await ads.sponsoredSlot({ context: { tool: "search_flights" } });
公式TS SDKで構築されたMCP サーバーでも、同じく1回の呼び出しで利用できます。すべてのツールにデータフィールドとウィジェットが自動的に追加されます。
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { enableLuluAds } from "lulu-ads/mcp";
const server = new McpServer({ name: "my-server", version: "1.0.0" });
await enableLuluAds(server, { endpointUrl: "https://my-server.example.com/mcp" });
まだパブリッシャーIDをお持ちでない場合は、docs/quickstart.mdをご覧ください。取得方法は3通りあり、いずれも他の手続きが前提条件になることはありません。
段階的な料金設定(無料プランでは広告あり、有料プランでは広告なし)を使いたいですか? enabledを渡すだけです。独自のサブスクリプション確認によって値を決められるため、別途デプロイしたり、条件分岐をあちこちに散らしたりする必要はありません。
result["sponsored"] = await ads.sponsored_slot(
context={"tool": "search_flights"},
enabled=user.tier != "paid", # False resolves instantly, no network call
)
result.sponsored = await ads.sponsoredSlot({
context: { tool: "search_flights" },
enabled: user.tier !== "paid",
});
フレームワークとの統合
| スタック | ひと言で言うと | ドキュメント |
|---|---|---|
| FastMCP(Python)、データ+ウィジェット | enable_lulu_ads(mcp, endpoint_url=...) | → |
| FastMCP(Python)、データのみ | mcp.add_middleware(LuluAdsMiddleware()) | → |
| MCP TS SDK、データ+ウィジェット | await enableLuluAds(server, { endpointUrl }) | → |
| LangChain / LangGraph(Python) | middleware=[LuluAdsAgentMiddleware()] | → |
| CrewAI(Python) | lulu_crewai.install() | → |
| MCP TS SDK、データのみ | withLuluAds(server) | → |
| Skybridge(TypeScript) | withLuluAdsSkybridge(server) | → |
| ランタイムの管理者(チャットボット、WhatsApp/Telegram エージェント) | model_output + format_suffix(sponsored) | → |
| その他のランタイム/言語 | 生のコントラクト上で sponsored_slot(context) | → |
結果ウィジェット — 独自のツール出力用テンプレート(0.8.5)
ひとつのウィジェットで、あらゆるホストに対応します。 フレームは3種類のブリッジに対応します。安定版の MCP
Apps(ui/initialize、2026-01-26)、ドラフト版のフォールバック、そして ChatGPT の
window.openai です。SDK は両方のテンプレートキー
(_meta.ui.resourceUri と openai/outputTemplate)および両方の CSP 形式を
自動的に登録します。レンダリング済みインプレッションのビーコンを含め、claude.ai と ChatGPT でのライブレンダリングを検証済みです(インプレッションは人が実際に目にしたものをカウントし、API 出力だけのものはカウントしません)。アップグレード後は、ChatGPT のプラグイン設定でコネクタを更新してください。ツールのメタデータはキャッシュされます。
SPONSORED ストリップ独自の形式は、個別に設定できます。 sponsor_template= を使います(このウィジェット自体の本文レイアウトである template= とは独立しています)。
register_result_widget(
mcp, "search_flights",
template="table-card",
mapping={"rows": "flights", "columns": [...]},
endpoint_url="https://my-server.example.com/mcp",
sponsor_template="flip-card", # or "carousel", "scratch-reveal" -- "card" is the default
)
対応ホスト
プレーンな sponsored JSON フィールドは、常時利用できるベースラインです。すべてのツール結果に、すべての MCP ホストで含まれます。単に辞書にキーを追加するだけなので、機能させるためにホスト固有のサポートは必要なく、モデルが表示するかどうかを独自に判断します。上記のレンダリングされた MCP Apps ウィジェットは追加機能であり、ホストが ui/initialize ハンドシェイクを実装している場合にのみ表示されます。この表では、実運用環境で独自に検証した情報がある場合はそれを、その他の場合は最新の調査(2026-08-25)に基づいて、ホストごとにどちらに該当するかを明示しています。ここでウィジェットのステータスを「稼働中」とするのは、独自に確認した場合、またはベンダーが検証可能な具体的な実装情報を公開している場合に限ります。「動作するはず」という一般的な想定だけでは該当しません。
調査したホストです。ロゴがあることだけでサポートを示すものではありません。 各ホストの実際の対応状況については、以下のステータス列をご覧ください。(Continue.devは上の一覧にはなく、表にのみ掲載されています。すでに提供終了した製品ですが、網羅性のために記載しています。)
| ホスト | MCP ツール呼び出し | レンダリングされたウィジェット | ステータス |
|---|---|---|---|
| Claude (claude.ai) | はい | はい | 本番環境で稼働し、検証済み — 実際のトラフィックで、レンダリングされたインプレッションビーコンが確認されています。 |
| ChatGPT | はい | はい | 本番環境で稼働し、検証済みです。 |
CopilotKit (@ag-ui/mcp-apps-middleware) | はい | 作業中 | 修正をレビュー中、PR #8。エンドツーエンドで未検証 — ツール検出のバグが見つかり修正されましたが、完全なチャット UI に対する修正のテストは行われておらず(その検証時には LLM を利用できませんでした)、npm/PyPI へのリリースもまだです。この PR がマージされ、実環境で検証されるまでは、CopilotKit をサポート対象として扱わないでください。プレーンな sponsored フィールドはこのバグの影響を受けず、すでに利用できます。 |
| VS Code(ネイティブ MCP + GitHub Copilot Chat エージェントモード) | はい | 稼働中との報告あり | Microsoft の 2026-01-26 のブログ投稿と現行ドキュメントでは、VS Code を「MCP Apps を完全にサポートする初の主要な AI コードエディター」と説明し、具体的で確認可能な実装の詳細(サンドボックス化された iframe、CSP ドメイン設定、ui/initialize ハンドシェイク、App SDK)を記載しています。信頼性はありますが、ベンダーによる主張であり、当方では独自に再現して確認できていません。プレーンな MCP ツール呼び出し(Copilot Chat エージェントモード)は v1.102 から GA です。 |
| Cursor | はい | 対応との報告あり、未検証 | 上流の modelcontextprotocol.io 拡張機能サポートマトリックスで MCP Apps の実装者として挙げられています。Cursor 自身のドキュメントではなくサードパーティーの掲載情報であるため、上記の VS Code/Goose のベンダー公開の詳細よりも根拠は弱いです。当方でも実環境で検証を試みました(2026-08-25)が、テストに進む前にブロックされました。Cursor の無料プランのエージェント利用上限(プロンプト 2 件)に達し、実際のツール呼び出しを行えませんでした。実際に試み、実際に阻まれましたが、依然として未確認です。この点を避けて主張しているわけではありません。 |
| Goose(Block / AAIF) | はい | 稼働中(実験的) | Goose の公式ドキュメントでは、ui/initialize ハンドシェイクとサンドボックス化された iframe によるレンダリング(Goose Desktop 1.19.1+)を確認できますが、「ドラフト仕様に基づく実験的なもので、実装は最小限であり、変更される可能性がある」と明記されています。稼働中ではあるものの不安定であり、レンダリングが保証される対象ではないものとして扱ってください。 |
| Grok(xAI)— grok.com コネクター、Grok Build CLI、xAI API Remote MCP Tools | はい | 確認できる根拠なし | 3 つすべての xAI サービスで MCP(プレーンなツール検出と呼び出し)に対応していますが、本調査の時点で、Grok が MCP Apps UI 拡張機能に対応しているとする公式ドキュメント、変更履歴、サードパーティーのホスト対応マトリックスは見つかりませんでした。スポンサー付きデータフィールドは引き続き利用でき、常時有効な JSON フォールバックを通じて、モデル自身の判断だけでレンダリングされます。リッチウィジェットのレンダリング先がないだけです。 |
| Windsurf(Codeium) | はい | 確認できる根拠なし | Windsurf の公式ドキュメントでは、「MCP サーバーのツール、リソース、プロンプト」のみをサポートすると記載されています。確認したサードパーティーの MCP Apps ホスト対応リストには、いずれも含まれていません。スポンサー付きデータフィールドは、常時有効な JSON フォールバックを通じて引き続き機能します。 |
| Cline(VS Code 拡張機能) | はい | 確認できる根拠なし | 成熟した MCP クライアント(ツール、リソース、プロンプト、組み込みの MCP マーケットプレイス)です。リポジトリ内のどこにも ui/initialize、ui://、iframe レンダリングのコードは見つかりませんでした。スポンサー付きデータフィールドは、常時有効な JSON フォールバックを通じて引き続き機能します。 |
| Zed エディター | はい | 確認できる根拠なし | Zed の公式ドキュメントには、「現在、MCP の Tools と Prompts 機能をサポートしている」と明記されており、Resources に基づく UI レンダリングには対応していません。スポンサー付きデータフィールドは、常時有効な JSON フォールバックを通じて引き続き機能します。 |
| Continue.dev | はい(過去に対応) | 確認できる根拠なし | 開発終了:2026 年 6 月に Cursor に買収され、continuedev/continue リポジトリは読み取り専用となり、以後の開発は行われていません。活動中はプレーンな MCP ツール/リソース/プロンプトに対応していましたが、MCP Apps ウィジェットをレンダリングしたことを示す根拠はありません。今後の統合対象としては適していません。網羅性のためにのみ記載しています。 |
ホストによって追加コードが不要な場合と必要な場合がある理由
ツールが「レンダリング可能な UI を備えている」ことをどのように通知するかについて、ホストごとに異なる規約が採用されています。ホストの規約が最初に実装したものと異なる場合、レンダリングが実行される前に検出がひそかに失敗します(CopilotKit でのこの問題は PR #8 で修正しました。2026-08-25)。確認できた規約をすべて記録し、ウィジェット対応ツールごとにそのすべてに対して登録します。これは追加のみで、置き換えは行わないため、あるシグナルを認識しないホストは単に無視します。これが、Claude と VS Code では追加コードなしでレンダリングされる(同じ規約を共有している)一方、CopilotKit では個別の修正が必要だった実際の理由です。また、以下の表にある「証拠が見つかっていない」は、そのとおりの意味です。つまり、まだ証拠が見つかっていないのであって、存在しない証拠があるわけではありません。
上記に記載のないもの(LangGraph Studio、社内独自のエージェントハーネス、そしてまだ調査していないすべてのホスト)については、未確認/未調査です。いずれの場合も、プレーンな sponsored フィールドはフェイルオープンで、以下の 保証 に記載のとおり、適切に機能を縮退できるよう設計されています。この表にないホストでのレンダリングを確認した場合は、イシューまたは PR を作成してください。このリストは網羅的ではなく、正確であることを目指しています。
この表は、チャットホストでのウィジェットレンダリングに関するものです。より全体像については、エージェント向け SDK/フレームワーク(ほとんどは MCP パススルーでデータフィールドにアクセスするため、専用アダプターは不要)、レスポンス末尾に追加するランタイム(WhatsApp/Telegram/Slack/SMS ボット、バックグラウンドエージェント)、AI アプリビルダー(未評価)、MCP のホスティング/レジストリ(この SDK の設計上、対象外)を含む 対応サーフェスをご覧ください。
UI を設計する必要はありません。事前定義された、ホストネイティブの品質を備えた 4 種類の結果ウィジェットから 1 つを選び、ツールの structuredContent フィールドをマッピングしてください。フレーム、デザイントークン、開示済みの SPONSORED ストリップは SDK で固定されています。ストリップは有効な sponsored ペイロードがある場合にのみレンダリングされ、常に一番下に表示され、必ずラベルが付きます。広告主のロゴが表示され、読み込めない場合は文字タイルにフォールバックします。本文からストリップを削除したり、スタイルを変更したりすることはできません。
テンプレート:stat-card(大きな値とチップ。条件に応じた背景を任意で設定可能)、table-card(見出し付きの行、等幅数字、最適な行の強調表示)、notice-card(判定グリフと詳細行)、carousel-card(スワイプ可能なオプションカード 3~8 枚)。
from lulu_ads.widgets import register_result_widget
# after your @mcp.tool definitions:
register_result_widget(
mcp, "get_weather",
template="stat-card",
mapping={
"eyebrow": "location.name",
"value": {"path": "temperature_c", "suffix": "°"},
"condition": "conditions",
"chips": [{"path": "humidity_pct", "prefix": "💧 ", "suffix": "%"}],
"atmosphere": "weather_code", # WMO code or words -> sky gradient
},
endpoint_url="https://my-server.example.com/mcp",
)
TypeScript:import { registerResultWidget } from "lulu-ads/widgets" — 同じテンプレートとマッピング形式を使用します。返された _meta を server.registerTool(...) に展開して渡してください。マッピングの各エントリーにはドット区切りのパス、または {path, prefix, suffix} を指定できます。テンプレートで対応できないケースでは、.lw-* プリミティブで構成したカスタムマークアップを受け付ける body_html= のエスケープハッチを利用できます。ツールに対してこれを呼び出すと、そのツールでは enable_lulu_ads による汎用のスポンサーカードが意図的に置き換えられます。スポンサーデータは引き続き渡され、ウィジェット独自のストリップにレンダリングされます。
ウィジェットのレンダリング(MCP Apps UI)
プレーンな sponsored フィールドは常に送信され、常に機能します。一部のホストでは、モデル自身の判断のみでカードとしてレンダリングされ、どこにも指示が必要ありません。MCP Apps 拡張(io.modelcontextprotocol/ui)に対応するホストでは、enable_lulu_ads/enableLuluAds(上記のクイックスタートを参照)が、実際にレンダリングされるウィジェットをすでに登録し、すべてのツールに自動で追加します。そのため、この行より下の設定は不要です。独立した手順として存在するのは、register_sponsored_widget() にサーバーの正確な公開エンドポイント URL が必要であり、LuluAdsMiddleware/withLuluAds だけではそれを知る方法がないためです。
ツールごとに制御することを優先しますか(ツールごとに異なるウィジェットを使う、または一部のツールだけにウィジェットを設定する場合)?enable_lulu_ads の代わりに、低レベルの構成要素を直接使用してください。
from fastmcp import FastMCP
from lulu_ads.widget import register_sponsored_widget
mcp = FastMCP("my-server")
sponsored_app = register_sponsored_widget(
mcp,
endpoint_url="https://my-server.example.com/mcp", # your public MCP connector URL
text="Save 15% at checkout",
url="https://example.com/deal",
logo="https://example.com/logo.png", # optional, see "Logos" below
)
@mcp.tool(app=sponsored_app)
def search(...): ...
Python ではなく Node で構築された MCP サーバー向けの、同じヘルパーと公式 TS SDK です
(登録は async です。返される前にロゴを取得する場合があります)。
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { registerSponsoredWidget } from "lulu-ads/widget";
const server = new McpServer({ name: "my-server", version: "1.0.0" });
const appMeta = await registerSponsoredWidget(server, {
endpointUrl: "https://my-server.example.com/mcp", // your public MCP connector URL
text: "Save 15% at checkout",
url: "https://example.com/deal",
logo: "https://example.com/logo.png", // optional, see "Logos" below
});
server.registerTool("search", { ...appMeta }, handler);
カード形式。 template= でウィジェットのビジュアルレイアウトを選択します。これは登録時に指定するもので、register_result_widget 自体の template=(上記の結果ウィジェット)と同じ形式です。デフォルトは "card"(上記のレイアウト)です。別の形式にするには、別の名前を渡します。
sponsored_app = register_sponsored_widget(
mcp,
endpoint_url="https://my-server.example.com/mcp",
text="Save 15% at checkout",
url="https://example.com/deal",
template="banner", # full-width horizontal strip -- see Templates below
)
const appMeta = await registerSponsoredWidget(server, {
endpointUrl: "https://my-server.example.com/mcp",
text: "Save 15% at checkout",
url: "https://example.com/deal",
template: "banner",
});
認識されない template を指定すると、(ロゴの取得などの)ネットワーク呼び出しを行う前にすぐにエラーが発生し、暗黙にフォールバックすることはありません。これは、register_result_widget のテンプレート検証がすでに備えている、同じフェイルファストの契約です。
"hero" では、追加オプションとして background_image/backgroundImage を指定できます。登録時に一度取得され、logo と同じ方法でインライン化される URL です。
sponsored_app = register_sponsored_widget(
mcp,
endpoint_url="https://my-server.example.com/mcp",
text="Save 15% at checkout",
url="https://example.com/deal",
template="hero",
background_image="https://example.com/hero-bg.jpg", # optional -- falls back to the gradient
)
テンプレート:
template= | レイアウト | 備考 |
|---|---|---|
"card"(デフォルト) | 縦積み:ラベル行、その下にロゴ+テキスト/CTA の行 | 手作業で構築された元のレイアウトです。既存のインテグレーターは、これまでどおり変更なく利用できます。 |
"banner" | 横一列:ラベル、ロゴ、テキスト/CTA をすべてインライン表示 | 縦積みカードでは縦方向のスペースを無駄にする、横長で高さの低いレイアウト(VS Code のサイドバー、幅の広い CLI ペインなど)に適しています。 |
"hero" | 全面表示の背景画像(または共有グラデーション)の上に、視認性を高めるスクリーンを重ねてロゴ/テキスト/CTA を配置 | background_image は任意で、インテグレーターが指定します。現時点では、ads-server の自動広告マッチングが独自に選択できるものではありません。 |
"flip-card" | タップ/クリックで 3D カードが反転し、表面にラベル、裏面にテキスト+CTA を表示 | 開示されたラベルは常に表面に表示されます。裏面に隠れるのは、オファー自体の詳細のみです。 |
"scratch-reveal" | オファーの上にスクラッチ式のキャンバスレイヤーを表示 | インタラクションの有無にかかわらず 3 秒後に自動表示されます。どちらの場合もオファーは同じで、これは装飾であり、コンテンツを出し分ける仕組みではありません。 |
"spin" | ロゴバッジに装飾的な回転と停止の演出を追加 | 決定論的です。常に実際のオファーが 1 つ表示され、結果が変わる仕組みではありません。 |
新しい形式は、提供開始に合わせてここに追加されます(カルーセル、比較、推薦コメント、カウントダウン、クイズ、動画など。Linear で管理しているギャラリーを参照してください)。すべてのテンプレートで、読み込み時のスケルトン表示、開示された Sponsored ラベル、「Powered by Lulu Ads」フッター、データが欠落または不正な形式の場合のフェイルオープンという、まったく同じ保証が適用されます。これらはテンプレートごとに変更できません。
enable_lulu_ads/enableLuluAds も、すべてのツールに対して、内部で代わりに同じ処理を行います。この手順をツールごとに正しく実施するのは忘れやすいことが、実運用で判明しました(2026-07-26)。自社のドッグフード用サーバーでも手作業で組み込まれていたのはツール 1 つだけで、それ以降に追加されたツールには、気づかれないまま一度も組み込まれていませんでした。ツールごとの手順なしに自動で適用したい場合は、これを直接使うのではなく、enable_lulu_ads/enableLuluAds を使用してください。
getlulu.dev と同じビジュアルシステムを採用した、角丸でグラデーションのフローティングカードを表示し、Sponsored ラベルを開示します。あくまでマークアップであり、指示ではありません。ホスト固有の 3 つの癖にも対応します。Claude では、エンドポイント URL から導出される、ドキュメントに記載のない _meta.ui.domain の値が必要です(ここでは自身で算出し、認証情報は使いません)。ウィジェットは読み込み時に ui/notifications/initialized のハンドシェイクを送信する必要があり、送信しないと Claude が iframe を非表示にしたままにします。また、ロゴはリンクではなくインライン化されます(次のセクションを参照)。これは、ウィジェットのサンドボックス固有の CSP によってロゴがひそかにブロックされるのを防ぐためです。本番環境(dali.getlulu.dev/mcp、ext-apps#671)で実稼働確認済みで、情報は 2026-07-19 時点のものです。この修正が反映される前は、Claude 独自の MCP Apps ウィジェット描画がプラットフォーム全体で壊れていました。そのため、ここに限らず、「描画されるはず」という主張は、自分のホストで実際に確認するまでは未検証として扱ってください。
ウィジェットは読み込み直後に shadcn の <Skeleton> を表示し、ライブのツール呼び出しが届いた時点でのみ実際のコンテンツに切り替わります。register_sponsored_widget() に渡した text/url/logo は初期コンテンツとしては表示されません。ライブパスで実際に使われるのは、それらのオプションのうち label/cta/accent* のみです(ワイヤーペイロードに含まれないフィールドのデフォルト値、およびインテグレーターごとの静的なブランドテーマとして使用されます)。実際のツール呼び出しのたびに、ウィジェットは MCP Apps ホスト独自の ui/notifications/tool-result プッシュを待ち受け(呼び出しごとに新しい iframe がマウントされ、再利用されません。「ツールごと」ではなく「呼び出しごと」というのはプロトコル上の保証であり、これを実現するためにサーバー側で何かを構築する必要はありません)、その呼び出しの structuredContent.sponsored を使って表示します。つまり、登録時に一度だけ組み込まれた固定ペイロードではなく、呼び出しごとのライブ広告コンテンツです。通知を一度も送信しないホストでは、スケルトンが無期限に表示されます(代わりの広告は表示されません。未解決の問題については js/widget-src/src/mcpBridge.ts の InitialOptions の docstring を参照してください)。sponsored フィールドのない呼び出し(通常のフェイルオープンケース)では、フッターだけの空のカードが表示されます。カード、スケルトン、「Powered by Lulu Ads」フッターは、Python と TypeScript の SDK でバイト単位まで同一の、コンパイル済み React/shadcn バンドル(js/widget-src/ にチェックインされ、両言語に埋め込まれています)を共有しており、どの状態でもフッターは常に同じ永続的なカードシェル内に表示されます。
ロゴ
logo に指定するのは、直接埋め込む URL ではなく、ブランドマークを取得する URL です。指定すると、SDK は登録時にその画像をダウンロードし、data: URI としてウィジェットにインライン化します。これは偶然の仕様ではありません。MCP Apps の仕様では、ホストはウィジェットのサンドボックス化された iframe 内で img-src 'self' data: <resourceDomains> を強制します。そのリソースの CSP 設定でロゴのドメインを別途宣言しない限り、<img src="https://your-cdn.com/logo.png"> は何のエラーもなく黙って破棄され、どのホストでもカードには空の領域だけが表示され続けます。data: URI は同じルールのもとで常に許可されるため、サーバー側で画像を取得してインライン化すれば、この問題を回避できます。適切に設定し忘れないよう気を付ける CSP 設定は必要ありません。
無効な logo や接続できない logo が登録を妨げることはありません。警告ログを出してスキップされ、logo を設定しなかった場合と同様にロゴなしでカードが表示されます。受け付ける形式は image/png、image/jpeg、image/svg+xml、image/webp、image/gif のみで、サイズ上限は 200KB です(カード内での表示サイズは 28×28 なので、これ以上のサイズをワイヤー経由で送る理由はありません)。
CLI での表示
ターミナルにはウィジェットを表示する領域がないため、出力はモデル自身のテキストだけです。開示された行に触れるかどうかは、実際にモデルの判断に委ねられています(強制されることは一切ありません。「保証」を参照してください)。LuluAdsMiddleware/withLuluAds は、initialize 時に送信される MCP の clientInfo.name を使って既知の CLI クライアント(現在は claude-code。ライブで確認済み)を検出し、CLI から接続された場合は、通常のフィールドに加えて、枠線付きのプレーンテキストカードを content[] に追加します。あくまでデータであり、モデルへの指示は一切ありません。モデルが内容を伝えると判断した場合に、単なる文章ではなく独立したブロックとして読めるように整形しています。
╭─ Sponsored ────────────────────────────────────╮
│ Search 700+ airlines in one place — Kiwi.com │
│ finds routes other search engines miss. │
╰─ via Lulu Ads ─────────────────────────────────╯
→ https://ads.getlulu.dev/c/9f2a1c
既知の制限について、曖昧にせずここで開示します。一部の MCP クライアントは、同じ結果に structuredContent が含まれていると、すべての content[] ブロックをモデルに転送しないことがあります。これは Claude Code におけるクライアント側の未解決バグで、2 回報告され、修正なしで 2 回クローズされています(#55677 → #45575 に集約 → 古い課題として自動クローズ)。Claude Code でのライブテスト(2026-07-21)では、structuredContent がある場合(出荷時のデフォルト)、枠付きカードはモデルに届きませんが、プレーンな sponsored フィールドは届きます。問題なく 3 回中 3 回、モデルは自身の言葉で、ラベル付きの「Sponsored: ...」という行として確実に表示しました。
cliTextMode — 上記のクライアントのバグに対するオプトインの修正
一見明らかな修正、つまり structuredContent を省いて
content[] と競合するものがない状態にする方法もテストしました。その結果は
content[] にほかに何が含まれているかによって、まったく異なりました。
- 広告のみで、実際のツールデータが併記されていない場合:カードは毎回表示されますが、モデルはプロンプトインジェクションの疑いがある試みと判断し、ユーザーに注意を促します。3 回中 3 回です。表示されないよりも悪い結果です。
- ツール自身の結果を完全にレンダリングしたものと広告が併記されている場合:カードは毎回表示され、モデルは通常の開示済み広告として扱い、中立的に言及します。3 回中 3 回です。疑いは持たれません。
つまり、この修正は有効ですが、ツール自体の動作が条件になります。この SDK ではそれを確認できないため、オプトインで、デフォルトではオフになっています。
mcp.add_middleware(LuluAdsMiddleware(cli_text_mode=True))
withLuluAds(server, ads, { cliTextMode: true });
ツールの content[] に、単独で結果を完全に人が読める形でレンダリングしたものがすでに含まれている場合に限り、これを有効にしてください。「structuredContent を参照」のようなプレースホルダーでは不十分です。有効にすると、outputSchema が宣言されていない CLI クライアントとして検出されたものでは structuredContent が削除されるため、content[](ツール自身のテキストと当社のカード)が確実にモデルに届きます。outputSchema を宣言するツールには影響しません。ここで structuredContent を削除すると、クライアント側のスキーマ検証が完全に機能しなくなります(確認済み:fastmcp.exceptions.ToolError 「outputSchema defined but no structured output returned」)。これはツール呼び出しの失敗であり、カードが表示されないよりも明らかに悪い結果です。この SDK は、カードの表示を優先して、スキーマを宣言したツールの structuredContent.sponsored を削除することはありません。cliTextMode の設定にかかわらず同じです。
上流のクライアントのバグが修正されるまでは、CLI カードは「オプトインし、ツールが条件を満たせば確実にレンダリングされるもの。いずれにしても機能する開示に加えて表示される」と考えてください。上記のウィジェットの経路と同様、ご自身のホストでの確認が必要です。
stdio サーバー
「ウィジェットのレンダリング」セクションより前の内容はすべて、stdio トランスポートのサーバーでもそのまま機能します。SDK はコードからインポートして呼び出すライブラリであり、ご自身のサーバーがクライアントとどのように通信するかは認識せず、関与もしません。通常の sponsored データフィールド(LuluAdsMiddleware / mcp.add_middleware()、withLuluAds(server))はエンドポイント引数を取らず、ads.getlulu.dev/slot へ通常のアウトバウンド HTTPS リクエストを送信します。プロセスが長時間稼働するリモートサーバーであっても、npx/uvx で起動されたローカルサーバーであっても、リクエストは同じです。ここで実際によく使われるのは CLI のテキストカード経路(上記の「CLI レンダリング」を参照)です。Claude Code はほとんどの MCP サーバーを stdio 経由で起動します。これはまさに、この SDK がすでに検出し、開示済みのプレーンテキストカードをレンダリングするクライアントです。
レンダリングされる MCP Apps ウィジェットだけは適用されません — enable_lulu_ads/enableLuluAds と、より低レベルの register_sponsored_widget/registerSponsoredWidget は、いずれも実際の endpoint_url を必要とします。これは、ウィジェットの iframe の CSP に使われる、Claude の非公開な _meta.ui.domain 値にハッシュ化されます。これは Lulu Ads の制限ではありません。MCP Apps の ui/initialize ハンドシェイクは、ホストとサーバー独自の HTTP エンドポイント間のネットワークプロトコルであり、stdio サーバーにはそのエンドポイントがありません。サーバーが stdio 専用の場合は、LuluAdsMiddleware/mcp.add_middleware()(TypeScript では withLuluAds(server))を直接呼び出してください。enable_lulu_ads は決して使わないでください。エンドポイントの設定は不要で、データフィールドと CLI のテキストカードを利用できます。
パブリッシャー側の注意点:マーケットプレイスの自動「monetized」バッジは現在、remote_url によってリスティングと登録済みパブリッシャーアカウントを照合しています。stdio のリスティングには remote_url がないため、SDK を統合して収益を得ていても自動でバッジは付きません。SDK と収益化の経路自体には影響しません。これはマーケットプレイスのリスティング表示に限った問題で、別途追跡されています。
保証事項(約束だけでなく、コードで実施)
| 保証事項 | 方法 |
|---|---|
| 広告が原因でツール呼び出しが失敗することはありません | すべての失敗経路で None/null を返します。ハードな 800ms の実時間タイムアウトを設定しています(呼び出しがサーバー側の分類を伴う場合は 3000ms) |
| 常に広告であることを明示します | label: "Sponsored" は SDK が設定し、レスポンス本文から取得することはありません |
| プロンプトインジェクションは一切ありません | データフィールドを提供します。契約のどこにも表示指示はありません |
| 個人識別情報(PII)がサーバーの外部に出ることはありません | リクエストを作成する前に、クライアント側で context を許可リストに照らしてフィルタリングします |
| 品質審査を実施します | すべてのクリエイティブは、スロットに入る前に Dali のスコアリング(70 以上)を通過します |
| ID ではなく、意図を基準にします | ターゲティングには、この呼び出しで明示されたコンテキストのみを使用します。ユーザープロファイルやセッションをまたぐ ID は使用しません |
| 設定に不備があっても安全です | 認証情報が不足している場合、クライアントは何もしない状態になり、None/null を返し、ネットワーク呼び出しは一切行いません |
なぜ、単に…しないのか
…モデルに、返信でスポンサーに言及するよう指示しないのですか?
表示指示があると、挿入されたディレクティブをスキャンするレジストリによって MCP サーバーが掲載解除されます。私たちが提供するのは、label、text、url を含む単純なデータオブジェクトです。モデルに何かの表示方法や言い回しを指示するフィールドは、契約のどこにもありません。
…インプレッションを数えて、表示ごとに課金しないのですか? 「インプレッション」が発生するのは、モデルが実際に広告を表示した場合だけです。これはサーバー側から検証できず、簡単に不正利用でき、監査も困難です。課金対象は CPA のみで、署名済みのサーバー検証トークンを引き換えるクリックに対して課金します。支払いは申告ではなく、実際のユーザーアクションに対応します。
…より適切なターゲティングのために会話をスキャンしないのですか?
広告ターゲティングのために会話の記録を読むのは、プライバシー上の落とし穴です。ユーザーの発言すべてが広告ターゲティングデータになります。私たちが受け付けるのは、許可リストに登録された 6 つのコンテキストキー、tool、category、query、route、locale、country のみです。対象となるのは今回の呼び出しで明示された意図だけです。会話の記録、プロファイル、PII フィールドはスキーマに存在しません。
仕組み
tool call
│
▼
your tool's own result
│
▼
POST /slot (1500ms cap — 3000ms when classifying a raw prompt — allowlisted context only)
│
▼
labeled data field { label: "Sponsored", text, url } ← attached, never injected
│
▼
host / model judgment → renders it, or doesn't — not our call
│ user clicks
▼
GET /c/{token} → signed redirect, click recorded
│
▼
advertiser's affiliate rails → POST /postback on conversion
│
▼
70% publisher / 30% Lulu, on the ledger. Earnings accrue to your balance from the first audited conversion — cash out from $100.
ワイヤーレベルの詳細については、docs/contract.md をご覧ください。
ドキュメント:https://getlulu.dev/docs · クイックスタート · API 契約 · インテグレーション · パブリッシャー登録 · 品質審査: Dali · MIT
変更履歴
-
0.9.19 — SDK 自身について報告する内容を修正しました。
TypeScript クライアントの
SDK_VERSIONはハードコードされた"0.9.14"のままで、パッケージはその間に 4 回リリースされていました。また、この値は/slotにsdk_versionとして送信されるため、TypeScript を使うパブリッシャーは全員 0.9.14 を報告し、アップグレードのテレメトリから実際にアップグレードしたユーザーを判別できませんでした。現在は正しい値になっており、package.jsonと照合するテストで固定したため、値の更新を忘れたリリースはデータをひそかに壊すのではなく、テストスイートで失敗します。Python クライアントにはこのバグはなく、lulu_ads.__version__をインポートしています。ドキュメントの変更のみで、動作に変更はありません。
js/src/skybridge.tsのヘッダーには、アダプターが_metaのみを使用すると記載されていましたが、これは 0.9.15 で正しくなくなっていました。現在はdocs/integrations.mdと同じ 4 ケースのランディングテーブルが記載されています。また、PyPI に表示されるpython/README.mdはルート README と同期されておらず、4 リリース分遅れていたため、パッケージページには Skybridge について同じ古い説明が記載されていました。現在は、両方のパッケージ README がscripts/sync-readmes.shによって更新されます。 -
0.9.18 — Skybridge 2.x をサポートし、気づきにくい唯一の障害を防止します。
skybridge 2.x では、プロトコルミドルウェアの接続処理が
McpServer.connect()からアプリのリクエストパスに移されました。そのため、接続箇所はメジャーバージョンによって異なります。// 2.x — inside the app handler new Skybridge({ name, version, handler: (server) => { withLuluAdsSkybridge(server); return server.registerTool({ name: "t" }, handler); }}); // 1.x — on the server you connect yourself withLuluAdsSkybridge(server);2.x で 1.x の形式を使うと、ミドルウェアは何も適用されないチェーンに登録されます。エラーも広告もなく、デバッグする手がかりもありません。アダプターはこの状態を検出し(ミドルウェアは常にツールハンドラーより前に実行されるため、ツールが実行されているのにミドルウェアが一度も実行されていなければ、接続されていないと判断できます)、正しいコード例を一度だけ警告します。警告は表示されますが、例外はスローされません。 2つのメジャーバージョンの両方で検証済みです。
[email protected]と@2.0.0で 106/106 の結果を得ており、公開済みパッケージを使ったエンドツーエンドの実行例も確認しています。2.x では、慣例に沿ったcontentのみのツールは広告を_metaのみに保持するため、ビューではまずstructuredContentを読み、次に_metaにフォールバックする必要があります。examples/skybridge-views/のリファレンスビューはそのとおりに動作します。 -
0.9.15–0.9.17 — Skybridge が広告を配信できるようになり、SDK から呼び出し元が報告されます。
2.0.1 について: これらの変更は、廃止される前に一時的に
2.0.1として公開されていました。このメジャーバージョンでは既存の^0.9.x依存範囲がすべて対象外となり、package.jsonを手動で編集しない限り誰も変更を受け取れない状態になっていました。PyPI では取り下げられ、npm では後継バージョンに置き換えられています。0.9.x 系をご利用ください。失われたものはなく、バージョン番号が変更されただけです。Skybridge。 以前の
withLuluAdsSkybridgeは、sponsoredを_metaのみに追加していました。Skybridge の経路では_metaを読み取るものがありません。enableLuluAdsはそこでウィジェットを登録できず(Skybridge のregisterViewResourceはプライベートです)、CLI カードもありません。そのため、枠は取得・記録されても、誰にも表示されませんでした。実際の結果の形状を調べたところ、Skybridge ではcontent[]が空で届き、structuredContentに値が設定されることがわかりました(公式 SDK とは逆です)。つまり、ビューはstructuredContentを描画元として使用します。現在はそこに追加されます。ツールの形状 sponsoredの追加先outputSchemaがないstructuredContentと_metaoutputSchemaがある_metaのみ — 未定義のフィールドがあるとクライアントの検証に失敗するためwithLuluAdsSkybridgeより前に登録済み_metaのみ — スキーマの状態が不明なため、安全なデフォルトを使用mcpMiddlewareからはoutputSchemaを確認できないため、2引数のregisterToolラッパーを使い、登録時にスキーマフラグを取得します。クライアントの識別情報。 すべてのアダプターが MCP クライアント名を
context.clientとして転送するようになり、ads-server は実際のエージェントとディレクトリークローラーを識別できます。"client"がコンテキストの許可リストに追加されました。インテグレーター側のコード変更は不要です。fastmcp 4.x / MCP SDK v2 で発生していた、気づきにくい2つの不具合を修正。
clientInfoはclient_infoに名称変更されていました。古い表記は空のexcept節内で例外を発生させていたため、4.x のすべてのホストで_connected_client_nameがNoneを返していました。これにより、CLI のスポンサー付きカード(is_cli_client(None)は False)も気づかれないまま無効になっていました。また、4.x はinitializeではなくserver/discoverをネゴシエーションに使用するため、on_initializeが呼び出されず、非同期ウォームアップが停止していました。その結果、段階的タイムアウト処理が回避しようとしていたコールドスタートの遅延が発生していました。どちらのアクセサーも新旧の表記を順に試すようになり、on_discoverはon_initializeと並行して実行されます。Python スイートは 4.0.5 と 3.4.4 の両方で 144 件成功/0 件失敗でした(修正前は8件失敗)。JS は 104/104 です。アップグレード: 0.9.x 系を維持するのは意図的です。
^0.9.xの範囲で 0.9.18 が対象となるため、npm update lulu-ads/pip install -U lulu-adsだけで十分で、依存範囲を編集する必要はありません。この変更が行き渡る仕組みこそ、廃止された 2.0.1 では失われていたものです。examples/skybridge_server.tsとnpm run verify:skybridgeを参照してください。これらはメモリー内トランスポートを介して実サーバーに対し、上記すべてを検証します。 -
0.9.14 — 広告ごとの
template(LUL-64。/slotがテンプレートを返す場合)が、スタンドアロンのスポンサー広告カードウィジェット(register_sponsored_widget()/registerSponsoredWidget())では、登録時に指定したtemplate=/template:より優先されるようになりました。値がない場合、またはこのバンドルビルドが認識するテンプレートでない場合は、これまでどおり登録時のデフォルト値にフォールバックし、それもなければ"card"にフォールバックします。アップグレード時に動作が変わりますが、コードの変更は不要です。 デフォルト以外のtemplate=(例:"hero")を指定して登録していた場合、独自のライブテンプレートを持つ広告は、今後そのテンプレートでレンダリングされます。オプトアウトはできません。これは、特定の広告に対して管理者が選んだテンプレートは、連携側が一律に指定したデフォルトより具体的な設定であるべきだからです。ただし、コードを変更していなくても、アップグレード後にウィジェットの表示が変わる可能性があります。また、ライブのtemplate値にプロトタイプチェーン上の名前(例:"constructor")が含まれると、それを「認識済み」と誤判定し、インプレッションビーコン送信後にレンダリングがクラッシュする可能性があった検索上の問題も修正しました。 -
0.9.13 — スタンドアロンのスポンサー広告カードウィジェット(
register_sponsored_widget()/registerSponsoredWidget()。React で構築された card、banner、flip-card、 scratch-reveal、spin、hero テンプレート)における、レンダリング済みインプレッションの課金漏れ(LUL-71)を修正しました。ワイヤーペイロードのimp_url/impUrlフィールドは両方のクライアント(Node SDK のSponsored.impUrl、Python クライアントのimp_url)で解析・公開されていましたが、React コンポーネントに届く前にウィジェットバンドル独自のメッセージ解析で黙って破棄されていました。そのため、広告を人が実際に見たことを確認するレンダリング済みインプレッションビーコン(1px の<img>。CPM 課金の基準になります)は、このウィジェットではリリース以来、どのテンプレートでも一度も送信されていませんでした。修正はApp.tsxの loading から「loaded」への遷移処理に集約しました。これは、各テンプレートの開示コンテンツが表示されるタイミングと同じです。flip-card では表面のティーザーを表示する時点で送信されるため、ユーザーがカードをめくる前に発火します。これはregister_result_widget()のフッターストリップですでに正しく行われていた動作と一致します。scratch-reveal では、誰かが削る前に送信されます。また、register_sponsored_widget()/registerSponsoredWidget()でも、ホストのデフォルトのimg-src 'self' data:ポリシーによってビーコン画像が黙ってブロックされないよう、ビーコンピクセルに必要なads.getlulu.devリソース/接続 CSP ドメインを宣言するようになりました。これはregister_result_widget()がすでに行っていた宣言と同じです。実環境でも検証済みです。imp_urlを含むtool-resultが届いた瞬間にビーコンが実際の HTTP リクエストを送信することを、ユニットテストだけでなく、ローカルサーバーのアクセスログでも確認しました。 -
0.9.12 — Result-widget フッターの
sponsor_template=(LUL-69)を、細い一行ストリップからカバーカードに刷新しました。カラフルなカバーバンド(ライブペイロードにcover_image_url/coverImageUrlがあれば実際のスポンサー画像を使用し、なければアニメーション付きグラデーション)、本文との境目に重なるロゴタイル、大きな文字、実際のピル型 CTA ボタンを備えています。"flip-card"のティーザーにも、単なるラベルではなく同じカバーのアイデンティティが表示されます。"spin"は削除しました。小さく、平坦なことも多い文字タイル型ロゴにコイン投げのようなscaleXアニメーションを付けても、実際には視認性が低いことがわかったためです(アニメーションの途中で、複雑なカバー背景に対してほとんど見えない細い線になります)。これは推測ではなく、実際の動作で確認しました。代わりに"carousel"を追加しました。同じ単一のスポンサー付きペイロードを、3 種類の見せ方(大きなブランドロゴ、オファーの文言、CTA)で自動的に切り替えます。複数のスポンサーを表示することはありません。このフレームには呼び出しごとにスポンサー付きペイロードが 1 件しか渡されないため、スタンドアロンギャラリーの carousel テンプレートとは異なり、ここでは複数の広告主が順番に表示されることはありません(別途未解決のバックエンド課題については LUL-49 を参照してください)。"scratch-reveal"は、宝くじ券のホイル風にスタイルを変更しました。金色のメタリックグラデーション、斜線模様、破線のミシン目、チケットのグリフを備え、操作があったことを実際に認識できるよう、自動表示までの時間を 3 秒から 5 秒に延長しました。勝ち負けの状態を持たない決定論的な表示である点は変わらず、これによってチケット風のデザインが実際のギャンブルの仕組みに該当しないようにしています。以前は"hero"も除外する理由だった「一行だけで、全面画像を表示する余地がない」という前提は、ストリップ自体がカバーの高さになったため、もはや当てはまりません。この点は未解決のフォローアップとして記録しており、このリリースでは対応していません。 -
0.9.11 —
register_result_widget()/registerResultWidget()にsponsor_template=/sponsorTemplate:(LUL-69)を追加しました。Result ウィジェットのフッターに組み込まれた SPONSORED ストリップ(stat-card/table-card/notice-card/carousel-card。スタンドアロンのスポンサー広告カードウィジェットではなく、パブリッシャー独自のツール出力)でも、表示形式を選べるようになりました。ウィジェット本体のレイアウトを指定するtemplate=とは独立しています。"card"(デフォルト)は、常に表示される従来のストリップです。"flip-card"は、タップすると実際のオファーをクロスフェードで表示するコンパクトな「Sponsored」ティーザーです。表面には開示ラベルだけでなく、「タップして表示 →」という実際のプロンプトを表示し、タップする理由を設けています。"spin"はロゴバッジに装飾的な回転から停止までの演出を加えます。コンテンツは最初から表示され、決して隠されません(結果が変化する仕組みはありません。下記の spin に関するガードレールを参照してください)。"scratch-reveal"はキャンバスのスクラッチオフレイヤーで、操作の有無にかかわらず 3 秒後に自動表示します。下のオファーは、誰かが削っても削らなくても同じです。"banner"/"hero"は意図的に提供していません。このストリップはすでに横一列なので banner は何も変わらず、全面背景は細いフッターバーに収まりません。これは、上記のスタンドアロンスポンサー広告カードギャラリー(register_sponsored_widget()独自のtemplate=)とは別のシステムです。名前が重複するテンプレートは同じですが、2 つのフレームは設計上独立した実装のため、コードを共有せず、このフレーム独自のバニラ JS レンダラーに移植しています。 -
0.9.10 — さらに 4 つのテンプレートを追加しました。
"flip-card"(LUL-53)はタップすると CSS 3D フリップで裏面のオファーを表示します。表面では常に最初に開示ラベルを表示し、これが何かを隠すことはありません。隠すのはオファーの詳細だけです。"scratch-reveal"(LUL-52)はキャンバスのスクラッチオフレイヤーで、操作の有無にかかわらず 3 秒後に自動表示します。下のオファーは、誰かが削っても削らなくても同じです。これは表示のアニメーションであり、コンテンツを隠す仕組みではありません。"spin"(LUL-54)はロゴバッジに装飾的な回転から停止までの演出を加えます。ギャンブルの仕組みになるため、運試しゲームのように結果が変化することはなく、設計上、結果は決定論的です。"hero"(LUL-48)は、背景画像またはグラデーションを全面に表示し、視認性を確保するスクリーン上にロゴ、テキスト、CTA を配置します。register_sponsored_widget()/registerSponsoredWidget()にも、"hero"用の省略可能なbackground_image/backgroundImageパラメーターを追加しました。logoと同様、取得は一度だけ行い、data:URI としてインライン化します(取得に失敗しても共有グラデーションにフォールバックし、登録エラーにはなりません)。これは、logo/accent*と同じカテゴリの、連携側が登録時に指定するブランディング情報です。ads-server によるキャンペーンごとの自動広告マッチングにはまだ接続されていません。ads-server には独自の画像フィールドがないためです(別途追跡中)。 -
0.9.9 — テンプレートギャラリーに初の実用テンプレートを追加しました。
template="banner"(LUL-47)は、デフォルトの"card"の縦積みレイアウトに代わり、ラベル、ロゴ、テキスト/CTA を一列に並べた全幅の横長ストリップです。VS Code のサイドバーや幅広の CLI ペインなど、横幅は広く高さが限られた画面に適しています。すべてのテンプレートに共通する仕様を維持しています。同じ永続的なグラデーションシェル内に表示し、同じ開示ラベルと「Powered by Lulu Ads」フッターを備え、読み込み中は同じスケルトンを表示し、フェイルオープン動作も同じです。これらの保証はテンプレート固有ではありません。背景画像にはまだ対応していません(LUL-47 の仕様では「画像またはグラデーション」が認められていますが、実際の背景画像フィールドには広告主側の新しいスキーマが必要なため、別途追跡中です)。既存のアクセントトークンのグラデーションを使用します。ドキュメントを追加し、ウィジェットのレンダリングにtemplate=のライブ例と利用可能な形式の一覧表を記載しました。 -
0.9.8 —
register_sponsored_widget()/registerSponsoredWidget()にtemplate=パラメーター(LUL-46)を追加しました。形状はregister_result_widgetと同じで、キーワード専用、デフォルトは"card"です(現時点で利用できる唯一の形式で、完全な後方互換性があります。既存の連携側の呼び出し箇所の動作は変わりません)。認識されない値を指定すると、ネットワーク呼び出し(ロゴ取得など)の前に即座にエラーになります。これは登録時に連携側が選択する値であり、呼び出しごとの値ではありません。register_result_widget自体のテンプレートと同様、静的に「登録時にコンパイル済みバンドルへ組み込まれる」動作であり、配信される広告ごとに変わるものではありません。今回は基盤部分のみです。js/widget-src/のレジストリ/ディスパッチ機構が名前によるテンプレートの検索に対応しましたが、エントリーは引き続き既存の手作りレイアウトである"card"のみです。ほかのテンプレートは、それぞれの後続リリースで実装次第追加されます(banner、hero、carousel、comparison、flip-card、 scratch-reveal、spin、testimonial、countdown、quiz、video)。また、既存の実際のバージョンずれも修正しました。TS SDK 自体のSDK_VERSIONテレメトリー定数は、package.jsonが0.9.6に進んだ後も、かなり前のリリースからずっと0.9.0のままでした。今回これを合わせ、2 つのパッケージのバージョン番号も再び一致しました(以前の0.9.7は Python 専用で、TS パッケージは0.9.7を飛ばして0.9.8になりました)。 -
0.9.7(Python のみ)— CLI/テキストクライアントでも、配信確認済みビーコンを記録できるようになりました。通常のレンダリング済みインプレッションピクセル(
imp_url)は、スポンサー付きストリップを表示する瞬間に、レンダリングクライアント自体が取得します。ターミナル/CLI ホスト(Claude Code など)にはレンダリングエンジンがないため、これまで CLI で配信されたカードは、配信に 100% 成功した場合でも、まったくad_eventsに記録されていませんでした。ミドルウェアが、CLI クライアントのcontent[]にカードを追加した瞬間に、同じビーコン URL をミドルウェア自身が送信するようになりました(fire-and-forget、ノンブロッキング、src=cli_serverのタグ付き)。対象はis_cliでカードを追加する両方の経路です(ミドルウェアが独自に取得したスロット、およびツールに事前設定されたsponsored)。これは新たに設けた独立したイベントcli_card_deliveredとして記録されます。impression_renderedではなく、CPM の課金/支払いには決して算入されません。これは実際にピクセルが表示されたことを示すシグナルよりも意図的に弱いものです。カードがツールのレスポンスでサーバーから送信されたことは証明しますが、人が実際に見たことは証明しません。設定は不要です。imp_urlを含む既存のスロットは自動的にこの機能を利用します。(ads-server:/i/{token}が、独立したイベントタイプを記録するための省略可能なsrc=cli_serverクエリパラメーターを受け付けるようになりました。) -
0.9.6 — ドキュメントのみ:新しい 対応サーフェス ページを追加しました。各エージェントサーフェス(チャットホスト、エージェント SDK/フレームワーク、レスポンスサフィックスランタイム、AI アプリビルダー、MCP ホスティング/レジストリ)を、実際に SDK に接続する方法(直接アダプター、MCP プロトコルのパススルー(サーバーに Lulu Ads が接続済みならアダプター不要)、汎用の
format_suffix仕様、または実際に未評価)別に整理しています。このリポジトリのほかの部分と同じ根拠基準を適用しています。また、「stdio サーバー」の節を追加し、データフィールドの経路にはendpoint_urlが不要で、stdio トランスポートのサーバーでも変更なしで動作することを明確にしました。レンダリングされた MCP Apps ウィジェットだけはendpoint_urlが必要で、stdio には適用できません。また、lulu_ads.__version__も修正しました。パッケージが0.9.5として公開されていた一方で、値は0.9.0にずれていました。下記の 0.7.0 の項目と同種の問題で、2 つの値が一致し続けることを強制する仕組みがないため再発しました。ここで次に解決すべき実際の課題と考えてください。 -
0.9.2(Python のみ)— ミドルウェアのバグを修正しました。ツールが独自の
sponsoredフィールド(カテゴリ固有のクロスセルなど、ドキュメントに記載されたパターン)を設定すると、CLI クライアントのチェックが実行される前に、on_call_toolの「上書きしない」早期リターンが発生していました。そのため、CLI ホスト(Claude Code)では、そのツールに広告がまったく表示されませんでした。ウィジェットの表示面もなく、テキストカードによる代替手段もありませんでした。どちらも同じ早期終了でスキップされていました。クライアントチェックを先に実行するようになりました。事前設定されたsponsored値にも、ツール自身が選択した広告を使って CLI テキストカード処理が適用されます。 -
0.9.1 —
table-cardウィジェットにrowLinkを追加しました。これは、URL(予約/チェックアウトリンクなど)を解決する行ごとの省略可能なドットパスで、スポンサー付きストリップですでに使用されているホスト非依存のopenLink()に接続されています。解決可能な URL がない行は、これまでとまったく同じように表示されます。 -
0.9.0 — Skybridge(https://skybridge.tech)のサポート:
withLuluAdsSkybridge(server)(lulu-ads/skybridge)。Skybridge のMcpServer.registerToolは、nameをconfigに含めた 2 引数の(config, handler)形式です。withLuluAdsがラップする公式 SDK の 3 引数形式(name, config, handler)とは異なります。そのままwithLuluAdsを再利用すると、config オブジェクトをツール名と誤認します。新しいアダプターでは代わりに Skybridge 独自のmcpMiddleware("tools/call", ...)プロトコルフックを使用します。実際に配布されている[email protected]パッケージの型定義と、実際のInMemoryTransportによる往復通信で検証済みです。意図的に_metaのみに対応しています。ミドルウェアからは呼び出し結果は見えますが、ツールに登録されたoutputSchemaは見えないため、structuredContentには一切触れません。 -
0.8.1 — Result ウィジェットのテンプレートギャラリー(npm で短期間配信された、より目立つストリップデザインの 0.8.0 を置き換え):
lulu_ads.widgets/lulu-ads/widgetsに、定義済みテンプレート 4 種類(stat-card、table-card、notice-card、carousel-card)、デザイントークンと.lw-*プリミティブ、フレームに組み込まれた開示済み SPONSORED ストリップ(スロットの新しいlogo_urlによる広告主ロゴ、または文字タイルへのフォールバック)を追加しました。register_result_widget()は、すでに登録済みの FastMCP ツールをインプレースで変更します(または、app=を明示する場合は AppConfig を返します)。 -
0.7.4 — ウィジェット:スポンサー付きカードの iframe キャンバスが、ダークテーマのホスト上で不透明な白いボックスを描画しなくなりました。埋め込み iframe では
background: transparentだけでは不十分です。Chromium がキャンバスを透明のままにするのは、埋め込み先ドキュメントで使用されるカラースキームが埋め込み元と一致する場合に限られます。このドキュメントではカラースキームを指定しておらず(デフォルトはlight)、そのためダークテーマのホスト(例:ダークモードの claude.ai)ではカードの背後に白い背景が表示されていました。ウィジェットでcolor-scheme: light darkを宣言するようになり、ユーザーの優先カラースキームが適用されます。これにより、その設定に従うホスト(claude.ai はデフォルトで従います)では、ライトテーマとダークテーマの両方で一致します。ライトスキームおよびダークスキームの埋め込みページで実証的に検証済みです。(パッケージが 0.7.3 として公開されていた一方、0.7.2 にずれていたlulu_ads.__version__も合わせて更新しています。) -
0.7.0 — Claude.ai のリモートコネクター経由で、実際のサードパーティ製 MCP サーバーに対して実稼働中に発見された2つの実際のバグを修正しました。
- メッセージごとに再接続するホストで広告がまったく配信されない(確認済み: Claude.ai は会話ごとに1回ではなく、チャットメッセージごとに新しい MCP セッションを 開きます)。根本原因:この SDK の永続 HTTP 接続は、メッセージ間に実際のアイドル時間が あると切断状態になりますが、「これまでに一度でも成功したか」という初回だけのチェック しか保護されていませんでした。そのため、それ以降の切断状態の呼び出しには、引き続き 厳しい定常状態タイムアウトが適用され、失敗していました。最後に実際に成功してからの 経過時間を基準に、永続的なフラグではなく毎回切断状態を再チェックするよう修正しました。 また、定常状態の高速タイムアウト自体も 800ms → 1500ms (Python と TS)に引き上げました。実稼働データから、「ウォーム」状態の呼び出しでも 796-802ms になることがあり、旧上限を余裕で下回るどころか、ほぼ上限に達することが 分かったためです。
- 広告の取得には成功するが、モデルに表示されない。 FastMCP/MCP TS SDK は、
LuluAdsMiddleware/withLuluAdsの実行前に、ツールの元の戻り値からツール結果のcontent[]を一度だけ構築します。そのため、structuredContent(この SDK 独自の テストスイートで確認されていた唯一の対象)だけを変更しても、content[]は古いまま でした。実稼働環境で確認したところ、ワイヤー応答のstructuredContentにはsponsoredが確かに含まれていましたが、Claude.ai はcontent[]を読み取り、 そこにない内容を報告していました。安全な場合(自動生成された JSON テキストブロックが 1つの場合)に、両 SDK でcontent[]も同期するようにしました。これが見逃されたまま 初回リリースに至った、まさにこの問題を再現するリグレッションテストも追加しました。 - 新機能:
enable_lulu_ads()(Python)/enableLuluAds()(TS)を追加しました。 1回の呼び出しで、データフィールドとレンダリングされた MCP Apps ウィジェットの両方を、 現在および今後のすべてのツールに自動で組み込みます。既存のregister_sponsored_widget()/registerSponsoredWidget()と、ツールごとのapp=/_meta.uiも引き続き使えます。現在は、ツールごとに制御するための低レベルな 構成要素として説明しています。これらの方法ではツールごとに手動設定を忘れやすいという 問題がありましたが、まさにそれを解消します。自社の dogfood サーバーで実稼働中に 見つかった問題で、ウィジェットを手作業で組み込んでいたのは1つのツールだけで、 その後追加したツールには更新が反映されないままでした。
-
0.6.2 — スポンサーカードが読み込み済み状態(実際に広告が配信された状態)に 落ち着くと、カード上を斜めの光が一度だけ走るようになりました。純粋な CSS (
js/widget-src/src/index.cssの.card-shine)で実装しており、実際のチャットスレッドに インライン表示されるため、繰り返し光るアニメーションではなく、マウントごとに正確に 1回だけ再生されます。また、prefers-reduced-motionに対応しています。スケルトンと 広告なしの状態には影響しません。 -
0.6.1 — ソースのマージ後に
dist/を再ビルドせず npm に公開されていた古い0.6.0を修正しました(js/にはprepublishOnlyのビルド手順がありません)。 npm 上の 0.6.0 は非推奨として、こちらを案内しています。また、README.md と両言語のwidget.py/widget.tsのドキュメント文字列も修正しました。register_sponsored_widget()に渡すtext/url/logoは、ライブのtool-resultが届くまで代替の「自社広告」として表示される、と誤って説明していましたが、 実際には表示されません。ホストからプッシュされなければ、ウィジェットはスケルトンを 表示し続けます。 -
0.6.0 — MCP Apps のスポンサードウィジェットで、登録時に組み込まれた固定の自社広告 ではなく、呼び出しごとのリアルタイムな広告コンテンツを表示するようになりました。 React + shadcn/ui(
Card、Skeleton、Button)で再構築し、両 SDK でバイト単位まで 共通の単一自己完結型バンドルにコンパイルしています(js/widget-src/)。ウィジェットは 読み込み時にすぐスケルトンを表示し、その後、MCP Apps ホスト独自のui/notifications/tool-resultプッシュを待ち受けます。仕様上、このプッシュはすでに 呼び出しごとに配信され、呼び出しごとに新しい iframe が使われます。サーバー側の変更は 必要ありません。プッシュを受け取ると、その呼び出しの実際のstructuredContent.sponsoredデータに切り替わります。ホストからプッシュされなければ スケルトンを表示し続け、代替広告は表示しません。ライブ経路で実際に使われるのは、register_sponsored_widget()/registerSponsoredWidget()のオプションのうち、label/cta/accent*のみです。「Powered by Lulu Ads」フッターはすぐに一度だけ 描画され、スケルトンからカードへの切り替えの一部にはなりません。使い捨てのテスト サーバーを使い、実際のホスト(claude.ai)で動作を確認しました。ツール呼び出しの解決前に スケルトンが描画され、解決後に実際の呼び出しごとのカードに切り替わること、切り替え中に フッターが消えたりレイアウトがずれたりしないこと、同じターン内の2回のツール呼び出しで 完全に独立した2つのウィジェットインスタンスが描画され、それぞれ自分の呼び出しのデータ だけを表示することを確認しました。これにより、この機能の基盤となる「ツールごとではなく 呼び出しごと」の動作が確認できました。(CTA のui/open-linkリダイレクト(通常の ナビゲーションではないこと)は、今回の検証でも静的コード検査とこのリポジトリの既存 ユニットテストで再確認しました。実際のクリック操作の記録も試みましたが、製品の不具合が 確認されたのではなく、ホストの二重サンドボックス化された iframe 内部に到達する ブラウザー自動化ツールの制約により阻まれました。) -
0.4.0 — LangChain の
LuluAdsAgentMiddleware、CrewAI のinstall()、 TypeScript のwithLuluAdsで、構築時に自動的に事前接続するようになりました (すでに対応済みの FastMCPLuluAdsMiddlewareと動作を統一)。また、FastMCP のLuluAdsMiddlewareと LangChain のLuluAdsAgentMiddlewareは、awaitされるsponsored_slot()のトラフィックが実際に使う 非同期接続プールも ウォームアップするようになりました。上記の構築時のウォームアップは同期クライアント だけに作用していましたが、非同期経路が使うのは別のプールです。別のイベントループ用の 接続をバックグラウンドスレッドから安全に事前ウォームアップすることはできないため、LuluAds.async_warm_up()は、実際にサービスを提供するイベントループ上のフレームワーク ライフサイクルフック(FastMCP のon_initialize、LangChain のabefore_agent)から、 インスタンスごとに1回実行されます。同じauto_warm_upフラグで同期ウォームアップと 同様に制御されます(この非同期経路があるのは Python のみです。TypeScript のautoWarmUpは制御対象のプールが1つだけでした)。非同期経路を使うdali-mcpの コールドスタート問題を本番環境で解消する修正です。両方の基本クライアントに、成功した 結果のみを保持する短い TTL(デフォルト45秒)のキャッシュを追加しました。キーには、 解決済みカテゴリまたはプロンプトテキストのハッシュを使います。ドキュメントも修正し、 実際のデフォルトタイムアウトは一律300msではなく、適応型の高速経路800ms/分類経路 3000msであることを明記しました。 -
0.3.7 —
cliTextMode(オプトイン、デフォルトでは無効):structuredContentなしでもcontent[]だけで成立するツールに限り、Claude Code のcontent[]が破棄される問題を 実際に解消します。条件を満たすケースと満たさないケースの両方を実稼働環境でテスト済み です。「CLI rendering」を参照してください。outputSchemaが宣言されたツールには決して 適用されません(クライアント側のスキーマ検証が壊れるため。fastmcp.exceptions.ToolErrorで確認済み)。 -
0.3.6 —
LuluAdsMiddlewareの構築時に接続を自動ウォームアップするようになりました (auto_warm_up、デフォルトで有効)。実際に冷えた状態での最初のツール呼び出しは 804ms で、デフォルトの高速経路800msの上限に達しており、下回っていませんでした。LuluAds自体は引き続き自動ウォームアップしません(汎用クライアントで、コンストラクターの 副作用としてネットワーク呼び出しが行われるのは意外なため)。一方、ミドルウェアは 「1行の設定で、追加設定不要」という約束を担うため、自らウォームアップします。 -
0.3.5 —
LuluAdsMiddlewareのtimeout_msにハードコードされていたデフォルト 300ms を修正しました。この値によって、実際のネットワーク遅延時に、配信可能な実際の広告が 気付かれないまま破棄されていました。テストスイートのすべてのテストで即時応答のモック トランスポートを使っていたことが、問題に気付かずリリースされた理由です。デフォルトはNoneに変更し、LuluAds独自の条件付きデフォルト(800ms/3000ms)に委ねるように なりました。 -
0.3.4 — CLI カードに角丸と「via Lulu Ads」フッターを追加しました (Unicode の罫線文字のみを使用。Claude Code で実稼働テストを行い、モデルがツール出力を 読む前に生の ANSI カラーエスケープが削除されることを確認したため、カラー表示はそもそも 選択肢にありませんでした)。また、
structuredContentを削除してcontent[]を強制的に 通す方法も、実稼働テストの結果、明示的に採用しないことにしました。カードは表示されますが、 モデルがそれをプロンプトインジェクションの疑いがあるものとして警告し、ユーザーに避ける よう促すためです。カードがなくても通常のフィールドは正直に表示される現状より、悪い結果に なります。詳細は「CLI rendering」を参照してください。 -
0.3.3 — CLI に適応したレンダリングを追加しました。
LuluAdsMiddleware/withLuluAdsが、initialize時に送られる MCP のclientInfo.nameを使って既知の CLI クライアント(現在はclaude-code。実稼働環境で確認済み)を検出し、通常の フィールドに加えて、枠線付きのプレーンテキストカードをcontent[]に追加します。 ターミナルにはウィジェット表示領域がないため、これは上記の MCP Apps ウィジェットに相当する、 CLI で安全な方法です。あくまでデータであることに変わりはありません。一部のクライアントでcontent[]が転送されないことが確認されている制限については、「CLI rendering」を 参照してください。 -
0.3.0 —
register_sponsored_widget()/registerSponsoredWidget()にlogoオプションを追加しました。登録時にサーバー側で取得し、ウィジェット内にdata:URI として埋め込むため、ウィジェットのサンドボックスの CSP (img-src 'self' data: <resourceDomains>)のもとで表示できます。利用者側でresourceDomainsを設定する必要はありません。リモートロゴの URL をそのまま使うと、 どこにもエラーが表示されないまま、ロゴがひっそりと破棄されてしまうためです。ロゴが不正、 または到達不能でも登録は失敗せず、カードにロゴが表示されないだけです。TypeScript のregisterSponsoredWidget()は、返す前にロゴの取得が必要な場合があるため、asyncに なりました。既存の呼び出し箇所にawaitを追加してください。 -
0.2.0 —
register_sponsored_widget()(Python:lulu_ads.widget。TypeScript:lulu-ads/widgetにも対応。公式 MCP SDK):プレーンな JSON フィールドだけでなく、 サーバーに実際にレンダリングされる MCP Apps UI のスポンサードカードを登録します。 Claude が必要とする未公開の iframe ドメイン要件と、ui/notifications/initializedハンドシェイクを自動で処理します。 ext-apps#671 に対してdali.getlulu.dev/mcpで実稼働確認済みの修正を一般化したものです。同じエンドポイント URL では、両 SDK がバイト単位まで同一の_meta.ui.domain値を生成します。 -
0.1.1 — Python SDK に永続 HTTP クライアントを導入しました(呼び出しごとにクライアントを 構築すると、CPU に制約のあるコンテナーではスロットの予算全体を消費する可能性がありました。 クライアントは各
LuluAdsインスタンスにつき1回だけ作成し、キープアライブを使って再利用 します)。フェイルオープンの動作に変更はありません。 -
0.1.0 — 初回リリース:Python および TypeScript クライアント、FastMCP/ LangChain/LangGraph/CrewAI/MCP-TS アダプター、サフィックスヘルパー、MCP コンシェルジュによるオンボーディング。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。