みなさん、こんにちは!

TDX 2026 で発表された HXL(Headless Experience Layer) ですが、2026年8月後半よりベータ公開となりました。Salesforce のデータを Slackbot、ChatGPT や Claude といった外部の AI エージェントアプリに、リッチなカスタム UI(ウィジェット)として表示できます。しかも表示するのは静的なサンプルではなく、組織の実データです。

本記事では、その HXL ウィジェットを MCP(Model Context Protocol)サーバー経由で公開し、AI エージェントアプリに「取引先の商談サマリ」を表示するまでをの流れを解説します。完成すると、AI エージェントアプリで「株式会社アストロエンタープライズの商談状況サマリを教えて」などと話しかけるだけで、次のようなカードが返ってくるようになります。

HXL の仕様や API の詳細は、公式の Headless Experience Layer Developer Guide (Beta) に詳しくまとまっています。ただし内容は英語で、サンプルもオブジェクトのデータにはアクセスしないものでした。そこで本記事では、実際に Salesforce 組織のデータを扱いながら、日本語で一連の流れを解説することを目的に作成しました。公式ガイドとあわせて読むことで、より理解が深まるはずです。

対象読者は Salesforce 開発の経験がある方です。Apex、メタデータ、VS Code + Salesforce&Agentforce 関連の拡張機能、sf コマンドの基礎は前提とし、HXL・MCP まわりの新しい概念を重点的に解説します。

※注意

  • この記事は 2026 年 8 月時点の情報で作成しています。HXL / MCP まわりは進化が速いため、最新の仕様は公式ドキュメントもあわせてご確認ください。
  • 本番組織ではなく Sandbox / Developer Editio/ Scratch Org での検証をおすすめします。
  • 本記事の内容を試すには、組織のインスタンスが Summer ’26 Patch 14.4 以降である必要があります。パッチバージョンは Salesforce Status で自組織のインスタンスを検索すると確認できます。
  • 本手順は Developer Edition での動作を確認しています。

全体の流れ

作るファイルはやや多いですが、役割ごとに整理すると次の 7 ステップになります。

  1. Apex クラスで取引先の商談サマリーを取得する(データ取得ロジック)
  2. LightningType で戻り値の型とレンダラー(描画マッピング)を定義する
  3. UiWidgetBundle でウィジェットの見た目(UI)を作る
  4. GenAiFunction(Agent Action) で Apex を API カタログに登録する
  5. McpServerDefinition で MCP サーバーとして公開する
  6. デプロイする
  7. MCP サーバーを有効化し、AI エージェントアプリから接続して動作確認する

登場人物を図にすると、データの流れは次のようになります。

ポイントは、「データを取得する経路(tools)」と「見た目を定義する経路(resources)」が別々で、それを McpServerDefinition が束ねているところです。ここを押さえると、以降のファイル構成がすっきり理解できます。


事前準備

  • Salesforce 組織(検証には Developer Edition または Sandbox を推奨)
  • Salesforce CLI(sf)& VS Code + Salesforce & Agentforce の拡張機能

本記事では API バージョン 67.0 を前提にしています。

HXL を有効化する

まず、組織で HXL の機能を有効化しておきます。設定 のクイック検索で「ヘッドレスエクスペリエンスレイヤー」を検索し、設定ページを開いて有効化スイッチをオンにします。

この設定が表示されない場合は、組織のインスタンスが Summer ’26 Patch 14.4 以降になっているかを確認してください(パッチバージョンの確認方法は冒頭の注意を参照)。


📦 本記事で作成するコード一式は、GitHub リポジトリ hinabasfdc/hxl-mcp-widget-sample に公開しています。手を動かしながら読む場合は、あわせて参照してください。

ステップ 1:Apex で商談サマリーを取得する

まずはデータ取得の心臓部となる Apex クラスです。取引先名を受け取り、次の 3 つを返します。

  • 商談の総数
  • フェーズ(StageName)別の商談件数
  • 未クローズ(IsClosed = false)商談の合計金額

MCP のツールとして呼び出せるように、@InvocableMethod / @InvocableVariable で入出力を定義するのがポイントです。クラス全体は次のとおりです。


ステップ 2:LightningType で型とレンダラーを定義する

ここが HXL の肝です。Apex の戻り値を、そのままウィジェットに流し込めるわけではありません。LightningType が「どの Apex 型を、どのウィジェットの、どの属性にマッピングするか」を仲介します。

MCP のレスポンスは 3 層の入れ子(エンベロープ)になっています。

この 3 層に対応する LightningType をそれぞれ用意します。

2-1. 一番内側:Apex 型のラッパー

lightningTypes/accountOpportunitySummary/schema.json は、Apex の Summary クラスを LightningType として参照するだけのシンプルなものです。

@apexClassType/c__<クラス名>$<内部クラス名> という記法で、Apex の内部クラスを直接参照できます。

2-2. 中間層:OutputValues

lightningTypes/accountOpportunitySummaryOutputValues/schema.json は、Apex アクションのレスポンス payload(summary を持つ)を表します。lightning:tagsmcp を付けるのがポイントです。

2-3. 一番外側:Result エンベロープ

lightningTypes/accountOpportunitySummaryResult/schema.json が MCP に公開される最上位の型です。actionName / isSuccess / outputValues を持ちます。

2-4. レンダラー:属性のマッピング

そして最も重要なのが renderer.json です。これが 「入れ子になったデータのどこを、ウィジェットのどの属性に流し込むか」 を定義します。Result 型のレンダラーは、3 層をたどって値を取り出します。

  • "definition": "@widget/c/accountSummaryCard" で、次のステップで作るウィジェットを指定します。
  • {!$attrs.outputValues.summary.xxx} という式で、エンベロープの奥にある実データを取り出してウィジェットの属性に渡します。この階層(outputValues.summary.)を間違えると値が表示されないので注意してください。

ステップ 3:UiWidgetBundle でウィジェットの UI を作る

いよいよ見た目です。HXL のウィジェットは tile/* というコンポーネントを JSON でツリー状に組み合わせて作ります。

3-1. スキーマ(受け取る属性の宣言)

uiWidgets/accountSummaryCard/schema.json で、ウィジェットが受け取る属性を宣言します。

💡 フェーズ別件数は、Apex 側で フェーズA: 3件 / フェーズB: 2件 のような 1 本のテキスト(stageSummaryText)に整形して渡しています。

3-2. ウィジェット本体(tile ツリー)

uiWidgets/accountSummaryCard/accountSummaryCard.json がレイアウト定義です。全体は長いので、構造を先に示します。

ヘッダー部分(アバター+取引先名+商談数バッジ)の JSON は次のようになります。

未クローズ商談合計金額を強調して表示する統計カード部分はこうです。アイコンと大きな数値を組み合わせます。

⚠️ 現時点でのハマりどころ:tile/iconname は実在する値のみ
tile/iconname に存在しないアイコン名(例:currency)を指定すると、ウィジェット全体が NullPointerException でクラッシュします。エラーメッセージが「An unexpected error occurred」としか出ないため、原因の特定に時間がかかりました。briefcasedollar-sign など、実在するアイコン名を使ってください。

UiWidgetBundle のメタデータ(accountSummaryCard.uiwidget-meta.xml)は widgetTypeJSON にしておきます。


ステップ 4:GenAiFunction(Agent Action)で Apex を登録する

ここは見落としがちですが必須です。Apex クラスを MCP サーバーから呼び出せるようにするために、Agent アクションとして登録する必要があります。

genAiFunctions/Get_Account_Opportunity_Summary/Get_Account_Opportunity_Summary.genAiFunction-meta.xml:

入力スキーマ(input/schema.json)では、accountName をユーザー入力として受け取ることを宣言します。

出力スキーマ(output/schema.json)では、summary表示可能(isDisplayable として宣言します。これがウィジェット表示につながる重要なフラグです。


ステップ 5:McpServerDefinition で MCP サーバーとして公開する

最後に、これまでの部品を束ねる McpServerDefinition です。ここが「データ取得(tools)」と「見た目(resources)」を接続する結節点になります。

読み解きのポイント

  • <tools>データ取得の経路です。apiIdentifieraa:apex-<クラス名>apiSourceAPI_CATALOG が、ステップ 4 で登録した Agent Action を指しています。
  • <resources>見た目の経路です。resourceUriui://widget/lightningType/c__<LightningTypeBundleのAPI名> という形式で、ステップ 2 で作った Result 型を指します。
  • <uiResource><resourceName> の値(accountSummary)を一致させることで、ツールとリソース(=データと見た目)が紐づきます。

💡 現時点の仕様: CSP(connectDomains など)や _meta.ui.domain といった MCP プロトコルの内部設定は、Salesforce プラットフォームが自動生成します。開発者がメタデータで設定する項目ではありません。


ステップ 6:デプロイする

ソースを組織にデプロイします。依存関係の順(Apex → LightningType & UiWidget → GenAiFunction → McpServerDefinition)を意識するとエラーが減りますが、まとめてデプロイしても解決してくれることが多いです。

現時点の仕様: ⚠️ UiWidgetBundle のスキーマは「プロパティ削除」で失敗する
一度デプロイしたウィジェットの schema.json から既存プロパティを削除すると、Schema update contains breaking changes: ... プロパティは削除できません というエラーになります。属性を作り直したいときは、古いプロパティを残したまま新しいものを追加するのが安全です。


ステップ 7:MCP サーバーを有効化し、AI エージェントアプリから接続する

デプロイが終わったら、MCP サーバーを有効化します。これは現状 設定画面からの手動操作のみです。

有効化したら、外部クライアントアプリケーションを作成し、AI エージェントアプリ側でコネクタ(プラグイン)として登録します。この OAuth まわりの手順は、別記事「Salesforce Hosted MCP Server 使い始め」で詳しく解説されているので、そちらもあわせてご覧ください。

ChatGPT と Claude で試してみる

公開した MCP サーバーは、MCP に対応した AI エージェントアプリから接続できます。コネクタとして登録したうえで「SalesforceDev で United Oil の商談サマリを見せて」と話しかけると、AccountOpportunitySummary ツールが呼び出され、次のように同じカードが表示されます。

Claude の場合

ChatGPT の場合

SUM(Amount)GROUP BY StageName も、すべて組織の実データが反映されています。フィールドレベルセキュリティ(FLS)や共有ルールも尊重されるため、安心して社内データを扱えます。同じ MCP サーバーを Claude と ChatGPT のどちらから呼び出しても、同一のウィジェットが同じ見た目で表示される点にも注目してください。


要考慮事項:ウィジェットの変更が反映されない

ところで、ウィジェットの定義を更新後、デプロイは成功しているのに、AI エージェントアプリ側では古いレイアウトのままという現象に悩まされました。組織側のメタデータを取得して確認しても、ローカルと内容は一致しています。それでも新しい見た目にならないのです。

原因は、AI エージェントアプリ(クライアント)側が、ウィジェットのテンプレートを resourceUri の文字列をキーにキャッシュしているからのようです。AI エージェントアプリの内部仕様で詳細は不明ですがresourceUri が同じままだと、中身をいくら変えても古いテンプレートが使われ続けるように見えました。

実際、クライアント側のコネクタ設定を見ると、ui://widget/lightningType/c__accountOpportunitySummary... という resourceUri をキーに、ウィジェットのテンプレートに関する情報が保持されているのが確認できます。

(2026/8/28更新)

まずは、クライアント側での「ツールの更新」をお試しください。これでテンプレート情報が新しいものに更新されることが多いです。

それでも上手くいかない場合のひとまずの対策は resourceUri のバージョニングです。

  1. LightningTypeBundle を新しい API 名でコピーする(例:accountOpportunitySummaryResultaccountOpportunitySummaryResultV2
  2. McpServerDefinition<resourceUri><uiResource> を新しいものに更新する
  3. デプロイ後、AI エージェントアプリ側でコネクタ/プラグインを更新(再登録)する

なお、バージョニングが必要なのは resourceUri が指す LightningTypeBundle と McpServerDefinition だけです。UiWidgetBundle は同じ名前のまま上書きデプロイで問題ありません(クライアントは URI をキーにキャッシュしているため、URI が変われば中身も取り直されます)。Apex や GenAiFunction も影響を受けません。


おわりに

本記事では、Apex・LightningType・UiWidgetBundle・GenAiFunction・McpServerDefinition を組み合わせて、組織の実データ(取引先の商談サマリー)を AI エージェントアプリにリッチなカードとして表示する HXL カスタムウィジェットを作りました。

従来、AI エージェントアプリに返せるのはテキストや静的なサンプルが中心でした。HXL と MCP を使うことで、Salesforce の実データを、意味のある UI として、外部の AI 体験に直接埋め込めるようになります。FLS や共有ルールも尊重されるため、セキュリティ面でも安心です。

一度この 7 ステップの型を押さえてしまえば、Apex のクエリとウィジェットの tile ツリーを差し替えるだけで、ケース一覧・在庫状況・売上ダッシュボードなど、さまざまなカスタムウィジェットに応用できます。ぜひ Developer Edition で試してみてください。

参考資料