mcpField
W
widget-design

widget-design

github.com/xmcp-devデザイン・UI044 回インストール36 週間前に更新
リポジトリ

widget-design について

widget-designは、GPT AppsとMCP Apps向けの会話内ウィジェットを作る開発者向けデザインスキルです。コンパクトでサンドボックス化された表示に適した設定、レイアウト、操作、状態管理の指針をまとめています。

主な機能

  1. 01GPT AppsとMCP Appsの設定指針
  2. 02Reactとテンプレート文字列の選択
  3. 03コンパクトなウィジェットのレイアウト原則
  4. 04ローカル状態と非同期状態の管理
  5. 05CSPとウィジェットメタデータの確認項目

活用シーン

  1. 01ウィジェットの実装方法の選択
  2. 02会話内の単一画面ウィジェットの設計
  3. 03読み込み中とエラー状態の処理
  4. 04GPT Appsのウィジェットメタデータの確認

MCP 対応

MCP Appsでは、最小限の設定でReactコンポーネントをext-appsクライアントに表示する方法を説明しています。GPT Appsの設定も扱います。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/xmcp-dev/skills --skill widget-design

インストール後、Claude Code で/widget-designを入力して読み込まれたか確認します。

同梱ファイルreferences/design-principles.md

詳細

ウィジェット設計のベストプラクティス

判断フレームワーク

プラットフォームの選択

対象のクライアントに基づいて選択します。

  • GPT Apps:対象はChatGPTです。_meta.openai に widgetAccessible: true を設定する必要があります。
  • MCP Apps:対象は任意のext-appsクライアントです。最小限の設定で、自動的に動作します。
ハンドラーの種類の選択
シナリオハンドラー理由
ユーザー操作が必要(ボタン、入力)React(.tsx)フックによる状態管理
外部ウィジェットライブラリの表示テンプレートリテラルスクリプト/スタイルを読み込むだけ
ツールのパラメーターからの動的コンテンツReactpropsを自然に受け渡せる
状態を持たない静的HTMLテンプレートリテラルよりシンプルで、オーバーヘッドが少ない

経験則:迷った場合は、Reactから始めましょう。シンプルに始めるより、後から変換するほうが難しいためです。

ウィジェット設計の原則

1. ウィジェットはWebアプリではありません

ウィジェットは会話内にインラインで表示されます。設計上の制約は次のとおりです。

  • ナビゲーションなし:1画面で完結する操作のみ
  • 幅に制限あり:ほとんどのクライアントで最大約600~700px
  • サンドボックス化:外部リソースにはCSP宣言が必要です
  • 一時的:再レンダリングされることがあるため、状態の永続性に依存しないでください
2. すぐに価値を提供する

ユーザーの操作を必要とせずに、有用なコンテンツを表示します。

// Bad: Requires click to see anything
export default function Widget() {
  const [data, setData] = useState(null);
  return <button onClick={fetchData}>Load Data</button>;
}

// Good: Shows data immediately
export default function Widget({ query }) {
  const [data, setData] = useState(null);
  useEffect(() => { fetchData(query).then(setData); }, [query]);
  return data ? <Results data={data} /> : <Loading />;
}
3. 限られたスペースでの視覚的な階層

幅が限られているため、階層構造がより重要になります。

<div className="space-y-4">
  {/* Label: small, muted, uppercase */}
  <div className="text-sm text-zinc-500 uppercase tracking-wider">Temperature</div>
  
  {/* Value: large, prominent */}
  <div className="text-5xl font-light">72°F</div>
  
  {/* Supporting: medium, secondary */}
  <div className="text-zinc-400">Feels like 68°F</div>
</div>
4. すべてのインタラクティブ要素にフィードバックを用意する

ユーザーが要素を操作できることを視覚的に確認できるようにします。

// Bad: No visual feedback
<button className="px-4 py-2 bg-white/10">Click</button>

// Good: Hover + transition
<button className="px-4 py-2 bg-white/10 hover:bg-white/20 border border-white/10 hover:border-white/20 transition-all duration-200">
  Click
</button>

状態管理のガイドライン

状態をローカルかつシンプルに保つ

ウィジェットは分離されています。Redux、Zustand、外部状態管理は使用しません。

// Good: Local state with hooks
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
3つの状態を常に処理する

すべての非同期処理には3つの状態があります。それらすべてを処理してください。

export default function Widget() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchData()
      .then(setData)
      .catch(e => setError(e.message))
      .finally(() => setLoading(false));
  }, []);

  if (loading) return <Loading />;
  if (error) return <Error message={error} />;
  return <Display data={data} />;
}
クリック時ではなくマウント時に取得する

ウィジェットはすぐに価値を提供する必要があります。

// Bad: User must click to see data
<button onClick={() => fetchData()}>Load</button>

// Good: Fetch automatically
useEffect(() => { fetchData(); }, []);

よくある間違い

1. widgetAccessibleの設定漏れ(GPT Apps)

これがないと、ウィジェットは表示されません。

// Broken
_meta: { openai: { toolInvocation: { ... } } }

// Fixed
_meta: { openai: { widgetAccessible: true, toolInvocation: { ... } } }
2. CSPなしで外部データを取得する

リクエストが通知なくブロックされます。

// Broken: fetch fails silently
fetch('{{WEATHER_API_URL}}');

// Fixed: declare in metadata
_meta: {
  openai: {
    widgetCSP: { connect_domains: ["{{WEATHER_API_BASE_URL}}"] }
  }
}
3. 寸法のハードコード

画面サイズが異なると、ウィジェットが正しく表示されません。

// Bad
<div style={{ width: '800px' }}>...</div>

// Good
<div className="w-full max-w-2xl mx-auto">...</div>
4. エラーバウンダリーがない

クラッシュすると、ウィジェットは空白で表示されます。

// Always wrap risky operations
try {
  const result = JSON.parse(data);
} catch {
  return <Error message="Invalid data format" />;
}

プラットフォーム固有の注意事項

GPT Apps:ウィジェットとの通信には構造化コンテンツを使用する

ツールからウィジェットにデータを渡します。

return {
  structuredContent: { game: "doom", url: "..." },
  content: [{ type: "text", text: "Launching DOOM..." }],
};

ウィジェットは useToolOutput() フックを使って読み取ります。

MCP Apps:最小限の設定

MCP AppsはReactコンポーネントだけで動作します。

// This is enough for MCP Apps
export const metadata = { name: "widget", description: "..." };
export default function Widget() { return <div>Hello</div>; }

クイックリファレンス

GPT Appsのチェックリスト
  • メタデータに widgetAccessible: true を設定
  • toolInvocation メッセージは64文字未満
  • 外部ドメイン用のCSPを設定

参考資料

references/design-principles.md を参照してください。内容は次のとおりです。

  • ウィジェットの完全なサンプル(カウンター、天気、アーケードゲーム)
  • コンポーネントのパターン(カード、ボタン、タブ)
  • CSS Modulesのサンプル
  • GPT Appの申請要件
  • プロジェクト構成のテンプレート

コメント