synapse について
Synapseは、既存のMCPサーバー向けにReact UIを構築し、MCP Appsリソースとして提供するためのスキルです。SDKやホスト間の互換性、サーバー統合の手順を扱います。
主な機能
- 01React UIを単一HTMLにバンドル
- 02Synapseのフックとコンポーネントライブラリ
- 03ホスト機能に応じた動作
- 04ツールブリッジとリソースの統合
- 05ビルドと検証の手順
活用シーン
- 01MCPサーバーへのインタラクティブUI追加
- 02Claude、ChatGPT、NimbleBrainへの対応
- 03サーバー書き込み後のUIデータ更新
MCP 対応
バンドルしたUIをMCPリソースとして提供し、MCP Appsホストブリッジ経由で既存のサーバーツールを呼び出す方法を説明します。ホストへの登録と機能判定も扱います。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ nimblebraininc/ skills --skill synapseインストール後、Claude Code で/synapseを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/gotchas.mdhost-contract.mdportability.md詳細
Synapse — MCP サーバー向けの UI を構築する
MCP サーバーにインタラクティブな UI を追加します。この UI は、@nimblebrain/synapse と Vite を使って 1 つのインライン HTML ファイルにビルドされる React アプリです。サーバーから MCP リソース ui://<name>/main として配信され、postMessage ブリッジに接続されたサンドボックス化 iframe 内で、MCP Apps ホスト(Claude、ChatGPT、NimbleBrain)によってマウントされます。UI はこのブリッジを通じてサーバーの既存ツールを呼び出します。データレイヤーに依存せず、特別なサーバーフレームワークも必要ありません。すべてのホストは同じブリッジを使用します。宣言する機能はホストによって異なりますが、機能がない場合も SDK は予測可能な動作をします。コンポーネントを作成する前に、以下の移植性についてお読みください。
@nimblebrain/synapse@^0.20.0(npm で公開済み)を対象にしてください。依存するピアパッケージは @modelcontextprotocol/ext-apps@^1.7.5 および @modelcontextprotocol/sdk@^1.29.0 です。0.x ではキャレット範囲がマイナーバージョンをまたがないため、誰かが意図的に更新するまで、アプリは固定したバージョンを使い続けます。NimbleBrain で 0.20.0 を使うには、message、updateModelContext、および ai.nimblebrain/* 拡張機能を宣言するホストのリリースが必要です(nimblebrain#1239)。それ以前のホストでは、チャット、モデルコンテキスト、ホストアクション、キー転送は機能せず、ファイルピッカーは拒否されます。このパッケージ自体がドキュメントです。コードを書く前に、エクスポートされている型をお読みください。ガイドは synapse.nimblebrain.ai にあります。
事前確認 — SDK の型を読む(実際のドキュメント)
インストール済みパッケージ(node_modules/@nimblebrain/synapse/dist/*.d.ts)または公開リポジトリ(github.com/NimbleBrainInc/synapse)から、以下を確認してください。
reactエントリー —AppProviderとそのフック(useApp、useCallTool、useCallToolAsTask、useDataSync、useTheme、useHostContext、useModelContext、useSendMessage、useAction、useFileUpload、useToolResult、useToolInput、useResize)。uiエントリー — コンポーネントライブラリ(AppFrame、ListDetailLayout、ListRow、Table、Badge、Prose、ConfirmDialog、tokens、…)。スタイルを独自に実装しないでください。- 型のエクスポート —
ToolCallResult、Theme、Appインターフェース(hostCapabilitiesを含む)、HostCapabilityError、hostSupports、NIMBLEBRAIN_EXTENSIONS。
続いて references/gotchas.md(デバッグの手間を減らす、見落としやすい API の仕様)と references/host-contract.md(NimbleBrain のマニフェスト宣言とブリッジ)をお読みください。どちらも短いので、まず目を通してください。動作するか、それとも「なぜすべてのツール呼び出しが unauthenticated を返すのか」となるかの違いを知る手がかりになります。
移植性 — これは MCP アプリであり、NimbleBrain アプリではありません
Claude、ChatGPT、NimbleBrain はすべて MCP Apps を使用します。それぞれが ui/initialize の応答(hostCapabilities)で提供する機能を宣言し、Synapse のゲート付き呼び出しはすべて、送信前にその宣言を確認します。また、機能がない場合には、文書化された動作を 1 つだけ行います(resize はゲートされず、useDataSync はリッスンするだけです)。
- 応答を待つリクエストは、送信せずに
HostCapabilityErrorで拒否されます —useCallTool(serverTools)、readServerResource(serverResources)、downloadFile、useCallToolAsTask(タスク)、ファイルピッカー。 - 応答を待たない呼び出しは何も送信しません —
useSendMessage(message)、useModelContext(updateModelContext)、useAction、forwardKeys。openLinkは URL 自体を開きます。 - ホストを待つフックは初期値のままです — ホストが
notifications/resources/list_changedを中継しない場合(またはサーバーがステップ 5 で 通知を宣言していない場合)、useToolResult/useToolInputはnullのままで、useDataSyncは一度も呼び出されません。
移植可能なサブセットは、ハンドシェイク、テーマとホストコンテキスト、ツールの入力/結果、callTool、
sendMessage、updateModelContext、resize です。これを基に構築したアプリは、どこでも同じように動作します。
NimbleBrain 拡張機能は、synapse/action、synapse/request-file、synapse/keydown の 3 つだけで、
それぞれホストが hostCapabilities.experimental に ai.nimblebrain/<name> を宣言している場合にのみ使用します。
何もしない動作もユーザーには見えないため、宣言を読み取り、ホストが対応していない機能は非表示にしてください。
hostSupports(app, "requestFile")、app.hostCapabilities.message、app.supportsTasks を使います。
<AppProvider> はハンドシェイクが完了するまで何もレンダリングしないため、コンポーネントがこれらを読み取る前にすべて確定します。ハンドシェイクが返ってこない場合(ビルド済み HTML を直接開いた場合)は、プロバイダー配下は何もレンダリングされません。
フックごとの表、拡張機能の一覧、Claude と ChatGPT に同じアプリを提供する方法、2 つの接続エントリーポイントについては、references/portability.md を参照してください。
プロセス
-
サーバーを分析する — 言語(Python/FastMCP または TS)、トランスポート(stdio 対 HTTP ネイティブ/エッジ経由)、ツール一覧と戻り値の形式、デプロイ形態(
.mcpbバンドルかコンテナーイメージか。コンテナーの場合はステップ 7 で Node のビルドステージが必要です)。 -
ui/をスキャフォールドする —package.json(react/react-dom^19、@nimblebrain/synapse@^0.20.0、@modelcontextprotocol/ext-apps@^1.7.5、@modelcontextprotocol/sdk@^1.29.0、vite、vite-plugin-singlefile、typescript。Markdown をレンダリングする場合に限り、markedとdompurifyを追加)、vite.config.ts(react()、viteSingleFile()、synapseVite()、build.assetsInlineLimit: Infinity)、厳格なtsconfig.json、index.html、.gitignore(node_modules/、dist/、.vite/)を用意します。ビルドでnpm ciを実行できるように、package-lock.jsonをコミットしてください。 -
App.tsxを構築する —<AppProvider name="<server>" version="<version>">を使用します。副作用を伴うインポートは 1 つだけ Vite のエントリーポイント(main.tsx)に記述します:import "@nimblebrain/synapse/ui/base"(AppFrameが埋めるルートの高さの連鎖。最初の描画前に適用されます。注意点 M)。フォントはインポートしないでください — SDK にはフォントが含まれておらず、他のテーマ値と同様にホストからタイポグラフィが渡されます(注意点 N)。パッケージのAppFrameシェルを使い(AppFrame.Body bleed内にListDetailLayout/SidebarLayoutを配置)、独自にheight:100vhを実装しないでください(注意点 D)。データの取得にはuseCallTool(name)、またはuseApp().callTool(name, args)をラップした薄いuseCall<T>()を使います。サーバーが書き込みを通知したときに呼び出されるuseDataSync(() => …)で再取得します(ステップ 5)。コールバックではツール名を指定しないため、常に再取得して結果を返します。テーマにはtokens/useThemeを、エージェントコンテキストの送信にはuseModelContextを使います。マスターリストにはTableではなくListRowを使ってください(注意点 D —Tableは固定幅のレールからはみ出し、詳細ペインに重なって表示されます)。 -
レンダリングする HTML は必ずサニタイズしてください(保存型 XSS)。 サーバーまたはエージェントが作成した Markdown(メモ、説明、調査結果)を
Prose/dangerouslySetInnerHTML経由でレンダリングする場合は、必ず DOMPurify を先に実行してください:DOMPurify.sanitize(marked.parse(md, { async: false }) as string)。iframe はallow-scriptsとscript-src 'unsafe-inline'の CSP で実行されるため、注入された<script>/onerrorがツールブリッジの完全な権限で実行されるのを防ぐのは、CSP ではなくサニタイズだけです(ツールがアクセスできるすべてのものの読み取り/外部送信/変更)。プレーンな<Text>{value}</Text>は安全です(React がエスケープします)。一連の流れの詳細:gotcha K。 -
UI をリソースとして提供し、書き込みを通知してください。 ビルド済みの
ui/dist/index.htmlを返す@mcp.resource("ui://<name>/main")を使用し、仕様で定められた MIME タイプtext/html;profile=mcp-appで提供します(fastmcp3.4+ ではui://URI のデフォルトです。それ以外ではmime_typeを明示的に渡してください)。パスは環境変数(<APP>_UI_DIR)で解決し、__file__相対パスをフォールバックとして使用します(gotcha E — インストール済みパッケージは site-packages に配置されるため、__file__相対のui/dist検索では見つかりません)。ファイルをそのまま読み取るだけにしてください:DB/認証セッションは不要で、HTML に ID 情報を含めず、テナントデータはすべて実行時にブリッジ経由で取得します。その後、書き込みを行うすべてのツールで、書き込みのコミット後にnotifications/resources/list_changedを送信してください。これがuseDataSyncを起動する唯一の通知であり、すべての書き込み元(エージェント、別のビュー、Webhook)をカバーします:from fastmcp import Context from mcp.types import ResourceListChangedNotification @mcp.tool() async def save_item(item: Item, ctx: Context) -> dict: stored = store.save(item) await ctx.session.send_notification(ResourceListChangedNotification(), related_request_id=ctx.request_id) return storedrelated_request_idを指定すると、HTTP 経由で呼び出し自体のレスポンス内に通知を含められます。書き込みごとに一度通知し、行ごとには通知しないでください。ガイド全文:synapse.nimblebrain.ai/docs/guides/keep-ui-in-sync/。 -
アプリをホストに登録してください。 手順 2~5 で作成したバンドルはどちらも同じで、登録方法だけが異なります。NimbleBrain ホストを対象にする場合 —
manifest.jsonに_meta["ai.nimblebrain/host"]を追加します:placements[]エントリーを 1 つ指定します(slot: "sidebar.apps"、resourceUri: "ui://<name>/main"、route、label、icon)。登録仕様とオプションの詳細:references/host-contract.md。Claude または ChatGPT を対象にする場合 — ホストがツールで指定されたリソースをレンダリングします。ツールに_meta.ui.resourceUriを設定してそのリソースにバインドし、text/html;profile=mcp-appでそのリソースを 1 つだけ提供します(別の MIME タイプで 2 つ目のコピーを提供しないでください)。ui.visibilityとui.cspを宣言し、サーバーでサインインが必要な場合はsecuritySchemesと401チャレンジも設定します。Python サーバーでは、nimblebrain-synapseのSynapseUIがこれらすべてを設定します。その後、npx @nimblebrain/synapse check --target claude,chatgpt <server-url>を実行します。詳細:references/portability.md。 -
コンテナにデプロイするサーバー — マルチステージビルド。
.mcpbバンドルではリリース CI からui/distを取得しますが、コンテナイメージではビルドが必要です:node:22のビルダーステージでnpm ci && npm run buildを実行し、その後ランタイムでCOPY --from=builder …/ui/distを行います(ランタイムに Node を含めないため)。さらに UI ディレクトリの環境変数を設定します。ランタイムレイヤーからui/のソースを除外し、distだけを含めてください。 -
ローカルプレビュー —
cd ui && npm run dev→/__previewを開きます。エッジ経由で公開されるサーバー(HTTP ヘッダーから ID を取得し、DB を使用)の場合、stdio プレビューでは実サーバーを操作できないため、すべての呼び出しがunauthenticatedを返します。環境変数のフラグで有効にする シード済みの stdio モック をsynapseVite({ serverCmd })に指定してください(gotcha F)。 -
検証 —
npx tsc --noEmitとnpm run buildを実行します(Vite/esbuild はそれだけでは型チェックを行いません)。その後、UI の型チェックだけで済ませず、サーバープロジェクト独自の lint/format/test ゲート(例:make verify、ruff format --check)も実行してください。型が通っていても、フォーマットのみの差分で CI が失敗することがあります。プレビューをライトモードとダークモードに切り替え、すべての画面を目視で確認してください — テーマを考慮していない色指定はtsc/buildでは検出されず、片方のモードでのみ表示が崩れます。すべてのtokens.*は両方のモードで定義されていますが、独自の変数はそうではありません(gotcha L)。サーバーがui://<name>/mainをtext/html;profile=mcp-appとして提供すること、プレビュー経由の書き込みでビューが更新されること(プレビューはサーバーのlist_changedを中継します。gotcha F)を確認してください。
対象外
- ホスト側のレンダリング — ホストには配置 → iframe → ブリッジがすでに実装されています。必要なのはバンドルの構築と配置の宣言だけで、それ以上は不要です。
- 宣言型のホスト「コマンド」/ツールバー — そのような機能はありません。アプリ内のボタンは iframe 内に配置し、
useCallToolを使ってツールを呼び出します。